xaml
Связывание данных
Поиск…
Синтаксис
<TextBlock Text="{Binding Title}"/><TextBlock Text="{Binding Path=Title}"/><TextBlock> <TextBlock.Text> <Binding Path="Title"/> </TextBlock.Text> </TextBlock>
замечания
Все эти теги дают одинаковый результат.
Связывание строки с текстом
Чтобы изменить содержимое пользовательского интерфейса во время выполнения, вы можете использовать Binding . Когда привязанное свойство изменяется от кода, оно будет отображаться в пользовательском интерфейсе.
<TextBlock Text="{Binding Title}"/>
Чтобы уведомить пользовательский интерфейс об изменениях, свойство должно вызывать событие PropertyChanged из интерфейса INotifyPropertyChanged или вы можете использовать Dependency Property .
Связывание работает, если свойство «Заголовок» находится в файле xaml.cs или в классе Datacontext из XAML .
Datacontext может быть настроен непосредственно в 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>
Форматирование строковых привязок
Когда вы связываете что-то, например, дату, вы можете показать ее в определенном формате, не вникая с ней в код.
Для этого мы можем использовать свойство StringFormat.
Вот некоторые примеры:
Text="{Binding Path=ReleaseDate, StringFormat=dddd dd MMMM yyyy}"
Это форматирует мою дату следующим образом:
Вторник, 16 августа 2016 года
Вот еще один пример температуры.
Text="{Binding Path=Temp, StringFormat={}{0}°C}"
Это позволяет:
25 & deg; С
Основы INotifyPropertyChanged
Если вы не только хотите отображать статические объекты, но и ваш пользовательский интерфейс реагировать на изменения в коррелирующих объектах, вам нужно понять основы интерфейса INotifyPropertyChanged .
Предполагая, что наш MainWindow определяется как
<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>
С нашей моделью MainWindowViewModel класса MainWindowViewModel определенной как
namespace Application.ViewModels
{
public class MainWindowViewModel
{
private string _applicationStateText;
public string ApplicationStateText
{
get { return _applicationStateText; }
set { _applicationStateText = value; }
}
public MainWindowViewModel()
{
ApplicationStateText = "Hello World!";
}
}
}
TextBlock нашего приложения отобразит текст Hello Hello из-за его привязки. Если наш ApplicationStateText изменяется во время выполнения, наш пользовательский интерфейс не будет уведомлен об этом изменении.
Чтобы реализовать это, нашему DataSource, в данном случае нашему MainWindowViewModel , необходимо реализовать интерфейс INotifyPropertyChanged . Это приведет к тому, что наши Bindings смогут подписаться на PropertyChangedEvent .
Все, что нам нужно сделать, - это вызывать PropertyChangedEventHandler всякий раз, когда мы меняем наше свойство ApplicationStateText следующим образом:
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!";
}
}
}
и убедитесь в том, что наша Binding из TextBlock.Text на самом деле слушает 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>
Связывание с коллекцией объектов с INotifyPropertyChanged и INotifyCollectionChanged
Давайте предположим , что у вас есть ListView которым должен отображать каждый User объект в список под Users собственности ViewModel , где свойство объекта пользователя может получить обновленные программно.
<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>
Несмотря на то, что для INotifyPropertyChanged beeing реализовано правильно для объекта 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));
}
}
и для вашего объекта 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));
}
}
ваш пользовательский интерфейс не будет обновляться, если внесение изменений в пользователя осуществляется программно.
Это просто потому, что вы только установили INotifyPropertyChanged в экземпляре самого списка. Только если вы полностью повторно создаете экземпляр списка, если одно свойство элемента изменит ваш пользовательский интерфейс.// DO NOT DO THIS
User[] userCache = Users.ToArray();
Users = new List<User>(userCache);
Это, однако, очень утомительно и невероятно плохо для производительности.
Если у вас есть список из 100 000 элементов, в которых указаны как идентификатор, так и имя пользователя, будет создано 200 000 DataBindings, каждый из которых должен быть заново создан. Это приводит к заметной задержке с пользователем при каждом изменении.
System.ComponentModel.ObservableCollection<T> вместо List<T> : private ObservableCollection<User> _users;
public ObservableCollection<User> Users
{
get { return _users; }
set
{
if (_users == value) return;
_users = value;
NotifyPropertyChanged();
}
}
ObservableCollection предоставляет нам событие CollectionChanged и реализует INotifyPropertyChanged . Согласно MSDN, событие будет расти: «[..], когда элемент добавляется , удаляется , изменяется , перемещается или весь список обновляется ».
Однако вы быстро поймете, что с .NET 4.5.2 и ранее ObservableCollection не будет создавать событие CollectionChanged, если свойство элемента в коллекции изменяется, как описано здесь .
TrulyObservableCollection<T> без ограничения INotifyPropertyChanged для T имеющего все, что нам нужно, и выставлять smor T реализует INotifyPropertyChanged или нет: /*
* 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);
}
}
и определите наших Users недвижимости как TrulyObservableCollection<User> в нашей ViewModel
private TrulyObservableCollection<string> _users;
public TrulyObservableCollection<string> Users
{
get { return _users; }
set
{
if (_users == value) return;
_users = value;
NotifyPropertyChanged();
}
}
Наш пользовательский интерфейс теперь будет уведомлен о том, как когда-то INPC-Свойство элемента в коллекции изменится без необходимости повторного создания каждого отдельного Binding .