2022年3月28日 星期一

[WPF] DataBinding-進階篇(三)UserControl自定義屬性綁定ViewModel

 建立一個ViewModel並自定義屬性取代UserControl中的自定義屬性

ViewModel.cs:

    class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private int _labelValue;
        public int LabelValue
        {
            get => _labelValue;
            set
            {
                _labelValue = value;
                Console.WriteLine(_labelValue);
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("LabelValue"));
            }
        }
    }

INotifyPropertyChanged主要作用是通蜘UI介面對應的屬性值有改變了。

MainWindow中Slider的Value<=>ViewModel的LabelValue<=>UserControl中Label3的Content

在MainWindow要綁定ViewModel屬性方式跟綁定UserControl的屬性一樣,

分為使用code綁定或是在XAML內綁定。

MainWindow使用code綁定ViewModel屬性:

    public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModel();
        Slider1.SetBinding(Slider.ValueProperty, new Binding("LabelValue"));
    }

MainWindow的XAML內綁定ViewModel屬性:

<Window x:Class="WpfApp3.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp3" mc:Ignorable="d" Title="MainWindow" Height="400" Width="300"> <Window.DataContext> <local:ViewModel/> </Window.DataContext> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Slider x:Name="Slider1" Grid.Column="1" HorizontalAlignment="Center" Grid.Row="1" VerticalAlignment="Center" Width="250" ValueChanged="Slider1_ValueChanged" SmallChange="1" Maximum="100" Value="{Binding LabelValue}"/> <local:UserControl1 x:Name="UserControl_Labels" HorizontalAlignment="Center" VerticalAlignment="Center" LabelValue1="{Binding ElementName=Slider1, Path=Value}" LabelValue2="{Binding ElementName=Slider1, Path=Value}"/> </Grid> </Window>

以上這兩個方式是等效的。

設定DataContext為ViewModel,然後將Slider1的Value與ViewModel的LabelValue綁定。

UserControl用code做綁定:

    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
            Label1.SetBinding(ContentProperty, new Binding(nameof(LabelValue1)) { Source = this });
            Label3.SetBinding(ContentProperty, new Binding() {Path = new PropertyPath(nameof(ViewModel.LabelValue))});
            //這邊不需設定DataContext,會自動參考到ViewModel的LabelValue
            //但如果在這或是XAML內有設定全域性的DataContext,則這裡的綁定會失效。       
        }

        public object LabelValue1
        {
            get => GetValue(LabelValue1Property);
            set => SetValue(LabelValue1Property, value);
        }

        public static readonly DependencyProperty LabelValue1Property =
            DependencyProperty.Register("LabelValue1", typeof(object), typeof(UserControl1));

        public object LabelValue2
        {
            get => (object)GetValue(LabelValue2Property);
            set => SetValue(LabelValue2Property, value);
        }

        public static readonly DependencyProperty LabelValue2Property =
            DependencyProperty.Register("LabelValue2", typeof(object), typeof(UserControl1), new PropertyMetadata(3.0));
    }

UserControl的XAML內做綁定:

<UserControl x:Class="WpfApp3.UserControl1"

             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 

             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 

             xmlns:local="clr-namespace:WpfApp3"

             mc:Ignorable="d"

             d:DesignHeight="400" d:DesignWidth="400">


    <Grid>

        <Grid.RowDefinitions>

            <RowDefinition/>

            <RowDefinition/>

            <RowDefinition/>

        </Grid.RowDefinitions>

        

        <Label x:Name="Label1"

               Content="Label1" 

               HorizontalAlignment="Stretch"

               Grid.Row="0" 

               VerticalAlignment="Stretch" 

               HorizontalContentAlignment="Center"

               VerticalContentAlignment="Center"

               FontSize="36" 

               Background="#FFAA2525" 

               Foreground="#FFFBF8F6"/>

        <Label x:Name="Label2"

               d:DataContext="{d:DesignInstance Type=local:UserControl1}"

               DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:UserControl1}}"

               Content="{Binding LabelValue2}"

               HorizontalAlignment="Stretch"

               Grid.Row="1" 

               VerticalAlignment="Stretch"

               VerticalContentAlignment="Center" 

               HorizontalContentAlignment="Center"

               FontSize="36" 

               Background="#FF60B646"

               Foreground="#FFFBF8F6"/>

        <Label x:Name="Label3"

               d:DataContext="{d:DesignInstance Type=local:ViewModel}"

               Content="{Binding LabelValue}"

               HorizontalAlignment="Stretch"

               Grid.Row="2"

               VerticalAlignment="Stretch"

               VerticalContentAlignment="Center" 

               HorizontalContentAlignment="Center"

               FontSize="36"

               Background="#FF28389C"

               Foreground="#FFFBF8F6">

        </Label>

    </Grid>

</UserControl>

這比較不一樣的是 d:DataContext="{d:DesignInstance Type=local:UserControl1}"

以及d:DataContext="{d:DesignInstance Type=local:ViewModel}",

這兩行的效果是在設計階段引用參考實例,

在UI上顯示綁定後的值,如果是數值就顯示數字、如果是物件就顯示物件名稱。

PS.Label3不需設定DataContext。

DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:UserControl1}}"

屬於區域性的DataContext設定,如果設定全域性的DataContext會影響Label3綁定ViewModel的LabelValue。

PS.Label2綁定UserControl的LabelValue2,而Label3綁定的是ViewModel的LabelValue。

簡單說就是Label2是綁定自身屬性,Label3是綁定其他物件的屬性。

如果是參考單一ViewModel屬性或是自身屬性的話,只需設定全域性的DataContext。

沒有留言:

張貼留言