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.

Aby częściowo rozwiązać ten problem, możesz użyć 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 .

Po tym rozwiązaniu możemy po prostu wdrożyć naszą własną 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 .



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow