Поиск…


Синтаксис

  • <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 .



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow