xaml
Wiązanie danych
Szukaj…
Składnia
<TextBlock Text="{Binding Title}"/><TextBlock Text="{Binding Path=Title}"/><TextBlock> <TextBlock.Text> <Binding Path="Title"/> </TextBlock.Text> </TextBlock>
Uwagi
Wszystkie te tagi dają ten sam wynik.
Powiązanie łańcucha z właściwością Text
Aby zmienić zawartość interfejsu użytkownika w środowisku wykonawczym, możesz użyć Binding . Gdy powiązana właściwość zostanie zmieniona z kodu, zostanie wyświetlona w interfejsie użytkownika.
<TextBlock Text="{Binding Title}"/>
Aby powiadomić interfejs użytkownika o zmianach, właściwość musi wywołać zdarzenie PropertyChanged z interfejsu INotifyPropertyChanged lub można użyć Dependency Property .
Wiązanie działa, jeśli właściwość „Tytuł” znajduje się w pliku xaml.cs lub w klasie Datacontext z XAML .
Tekst danych można ustawić bezpośrednio w XAML
<Window x:Class="Application.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Application">
<Window.DataContext>
<local:DataContextClass/>
</Window.DataContext>
Formatowanie ciągów wiązań
Kiedy tworzysz powiązanie czegoś, na przykład datę, możesz chcieć pokazać go w określonym formacie, bez bałagania go w kodzie.
Aby to zrobić, możemy użyć właściwości StringFormat.
Oto kilka przykładów:
Text="{Binding Path=ReleaseDate, StringFormat=dddd dd MMMM yyyy}"
To formatuje moją datę do:
Wtorek, 16 sierpnia 2016 r
Oto kolejny przykład temperatury.
Text="{Binding Path=Temp, StringFormat={}{0}°C}"
To formatuje do:
25 ° C
Podstawy INotifyPropertyChanged
Jeśli chcesz nie tylko wyświetlać obiekty statyczne, ale Twój interfejs użytkownika reaguje na zmiany w obiektach korelujących, musisz zrozumieć podstawy interfejsu INotifyPropertyChanged .
Zakładając, że mamy zdefiniowane nasze MainWindow jako
<Window x:Class="Application.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:Application.ViewModels>
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<TextBlock Text={Binding Path=ApplicationStateText}" />
</Grid>
</Window>
Z naszym MainWindowViewModel klasy MainWindowViewModel zdefiniowanym jako
namespace Application.ViewModels
{
public class MainWindowViewModel
{
private string _applicationStateText;
public string ApplicationStateText
{
get { return _applicationStateText; }
set { _applicationStateText = value; }
}
public MainWindowViewModel()
{
ApplicationStateText = "Hello World!";
}
}
}
TextBlock naszej aplikacji wyświetli Text Hello World ze względu na jego powiązanie. Jeśli nasz ApplicationStateText zmieni się w czasie wykonywania, nasz interfejs użytkownika nie zostanie powiadomiony o takiej zmianie.
Aby to zaimplementować, nasze DataSource, w tym przypadku nasz MainWindowViewModel , musi zaimplementować interfejs INotifyPropertyChanged . Spowoduje to, że nasze Bindings będą mogły subskrybować PropertyChangedEvent .
Wszystko, co musimy zrobić, to wywołać PropertyChangedEventHandler za każdym razem, gdy zmieniamy naszą właściwość ApplicationStateText następujący sposób:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Application.ViewModels
{
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged( [CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _applicationStateText;
public string ApplicationStateText
{
get { return _applicationStateText; }
set
{
if (_applicationStateText != value)
{
_applicationStateText = value;
NotifyPropertyChanged();
}
}
}
public MainWindowViewModel()
{
ApplicationStateText = "Hello World!";
}
}
}
i upewnij się, że nasze Binding TextBlock.Text faktycznie nasłuchuje PropertyChangedEvent :
<Window x:Class="Application.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:Application.ViewModels">
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<TextBlock Text={Binding Path=ApplicationStateText, UpdateSourceTrigger=PropertyChanged }" />
</Grid>
</Window>
Powiązanie z kolekcją obiektów za pomocą INotifyPropertyChanged i INotifyCollectionChanged
Załóżmy, że masz ListView który ma wyświetlać każdy obiekt User wymieniony w właściwości Users ViewModel gdzie właściwości obiektu User można aktualizować programowo.
<ListView ItemsSource="{Binding Path=Users}" >
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type models:User}">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="5,3,15,3"
Text="{Binding Id, Mode=OneWay}" />
<TextBox Width="200"
Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Delay=450}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Pomimo, że dla INotifyPropertyChanged beeing zaimplementowano poprawnie dla obiektu User
public class User : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _id;
private string _name;
public int Id
{
get { return _id; }
private set
{
if (_id == value) return;
_id = value;
NotifyPropertyChanged();
}
}
public string Name
{
get { return _name; }
set
{
if (_name == value) return;
_name = value;
NotifyPropertyChanged();
}
}
public User(int id, string name)
{
Id = id;
Name = name;
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
i dla twojego obiektu ViewModel
public sealed class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<User> _users;
public List<User> Users
{
get { return _users; }
set
{
if (_users == value) return;
_users = value;
NotifyPropertyChanged();
}
}
public MainWindowViewModel()
{
Users = new List<User> {new User(1, "John Doe"), new User(2, "Jane Doe"), new User(3, "Foo Bar")};
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Twój interfejs użytkownika nie zostanie zaktualizowany, jeśli zmiana użytkownika zostanie wprowadzona programowo.
Jest tak po prostu dlatego, że ustawiłeś INotifyPropertyChanged tylko w samej instancji listy. Tylko jeśli całkowicie ponownie utworzysz listę, jeśli jedna właściwość elementu zmieni się, twój interfejs użytkownika zostanie zaktualizowany.// DO NOT DO THIS
User[] userCache = Users.ToArray();
Users = new List<User>(userCache);
Jest to jednak bardzo męczące i niewiarygodnie złe dla wydajności.
Jeśli masz listę 100'000 elementów przedstawiającą zarówno identyfikator, jak i imię użytkownika, na miejscu będzie 200'000 DataBindings, z których każdy musi zostać ponownie utworzony. Powoduje to zauważalne opóźnienie dla użytkownika, ilekroć zostanie dokonana zmiana w czymkolwiek.
System.ComponentModel.ObservableCollection<T> zamiast List<T> : private ObservableCollection<User> _users;
public ObservableCollection<User> Users
{
get { return _users; }
set
{
if (_users == value) return;
_users = value;
NotifyPropertyChanged();
}
}
ObservableCollection zapewnia nam zdarzenie CollectionChanged i implementuje samo INotifyPropertyChanged . Według MSDN wydarzenie wzrośnie: „[…] gdy element zostanie dodany , usunięty , zmieniony , przeniesiony lub cała lista zostanie odświeżona ”.
Szybko jednak zorientujesz się, że w .NET 4.5.2 i wcześniejszych ObservableCollection nie wywoła zdarzenia CollectionChanged, jeśli właściwość elementu w kolekcji ulegnie ObservableCollection jak omówiono tutaj .
TrulyObservableCollection<T> bez ograniczenia INotifyPropertyChanged , aby T miał wszystko, czego potrzebujemy i ujawniając, czy T implementuje INotifyPropertyChanged lub nie: /*
* Original Class by Simon @StackOverflow http://stackoverflow.com/a/5256827/3766034
* Removal of the INPC-Constraint by Jirajha @StackOverflow
* according to to suggestion of nikeee @StackOverflow http://stackoverflow.com/a/10718451/3766034
*/
public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
{
private readonly bool _inpcHookup;
public bool NotifyPropertyChangedHookup => _inpcHookup;
public TrulyObservableCollection()
{
CollectionChanged += TrulyObservableCollectionChanged;
_inpcHookup = typeof(INotifyPropertyChanged).GetTypeInfo().IsAssignableFrom(typeof(T));
}
public TrulyObservableCollection(IEnumerable<T> items) : this()
{
foreach (var item in items)
{
this.Add(item);
}
}
private void TrulyObservableCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (NotifyPropertyChangedHookup && e.NewItems != null && e.NewItems.Count > 0)
{
foreach (INotifyPropertyChanged item in e.NewItems)
{
item.PropertyChanged += ItemPropertyChanged;
}
}
if (NotifyPropertyChangedHookup && e.OldItems != null && e.OldItems.Count > 0)
{
foreach (INotifyPropertyChanged item in e.OldItems)
{
item.PropertyChanged -= ItemPropertyChanged;
}
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
OnCollectionChanged(args);
}
}
i zdefiniuj naszych Users właściwości jako TrulyObservableCollection<User> w naszym ViewModel
private TrulyObservableCollection<string> _users;
public TrulyObservableCollection<string> Users
{
get { return _users; }
set
{
if (_users == value) return;
_users = value;
NotifyPropertyChanged();
}
}
Nasz interfejs zostanie teraz powiadomiony o zmianie właściwości INPC elementu w kolekcji bez konieczności ponownego tworzenia każdego Binding .