【6月日新计划09】WPF入门-Binding和簡單的MVVM

342 阅读3分钟

【6月日新计划09】WPF入门-Binding和簡單的MVVM

图片.png

1. Binding

顧名思義,就是將獲取的數據和UI上的控件綁定起來,利用數據的變化來更新操作界面的內容

1.1 Target

綁定目標很好理解,融合控件都可以添加綁定屬性,如Button,ListBox。

1.2 Mode

图片.png

  • 通过 OneWay 绑定,对源属性的更改会自动更新目标属性,但对目标属性的更改不会传播回源属性。

  • 通过 TwoWay 绑定,更改源属性或目标属性时会自动更新另一方。

  • OneWayToSource 绑定与 OneWay 绑定相反;当目标属性更改时,它会更新源属性。

  • OneTime 该绑定会使源属性初始化目标属性,但不传播后续更改。 如果数据上下文发生更改,或者数据上下文中的对象发生更改,则更改不会在目标属性中反映。

1.3 Binding Command

常用到的屬性:

  • Source : 指向源对像的引用(数据的对像)
  • ElementName : 和x:Name綁定
  • DataContext : 和數據源綁定
  • RelativeSource : 相對于本身或者父元素
  • ItemSource : 绑定到集合元素
  • Path : 具體屬性

1.3.1 綁定屬性

1.什麼也不寫

如果什麼都沒綁,就是綁定DataContext,先從自己的開始找,沒有就去找父級,直到找到為止

//這種就會直接綁定到當前view對應的viewmodel(類)
<TextBox Width="200" Height="25" Text="{Bingding}"></TextBox>

2.Binding没有指定Source,也是使用DataContext

<Window x:Class="WpfApplication6.wnd636"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication6"
        Title="wnd636" Height="130" Width="300">
    <StackPanel>
        <StackPanel.DataContext>
            <local:Student2 Id="36" Name="wndTom"/>
        </StackPanel.DataContext>
        <Grid >
            <StackPanel>
                <!--没有指定Source,使用DataContext-->
                <TextBox Text="{Binding Path=Id}" Margin="5" BorderBrush="Black"/>
                <TextBox Text="{Binding Path=Name}" Margin="5" BorderBrush="Black"/>
                <Grid  DataContext="button33">
                    <Grid>
                        <!--没有指定Button的DataContext,则会使用Grid的。向下传递!-->
                        <Button x:Name="_btnOK" Margin="5" Height="22" Click="_btnOK_Click"/>
                    </Grid>
                </Grid>                
            </StackPanel>
        </Grid>
    </StackPanel>    
</Window>

3.簡單的屬性綁定

  • 可以是单个变量(int , double,string)

  • 也可以是一个数据集(List)

沒有source,則是綁定datacontext,而上面又沒有定義,只能是當前view對應的viewmodel類啦,而name則是這個類裡面的一個屬性

//前端頁面
<TextBox Width="200" Height="25" Text="{Bingding Name}"></TextBox>

//後端屬性
public string Name { get; set; }
  • path就是對應後台的值
    <TextBlock>
        <TextBlock.Text>
            <Binding Path="Name" />
        </TextBlock.Text>
    </TextBlock>

1.3.2 兩個物件互相綁定

  • ElementName用來綁定x:Name屬性的值

  • path則是對應這個控件的某個屬性

        <Grid Grid.Row="3">
            <StackPanel>
                <Slider x:Name="slider" Margin="8" Minimum="5" Maximum="100"/>
                <TextBox Text="Solider当前值:" Margin="8" Height="30"/>
                <TextBox Text="{Binding ElementName=slider,Path=Value,Mode=TwoWay}" Margin="8" Height="30"/>
                <TextBlock Text="Solider最小值:" Margin="8" Height="30"/>
                <TextBlock Text="{Binding ElementName=slider,Path=Minimum}" Margin="8" Height="30"/>
            </StackPanel>
        </Grid>

實戰

使用ItemSource和ElementName

    public class MainWindowViewModel : BindableBase
    {

        private ObservableCollection<MenuBar> menuBars;

        public ObservableCollection<MenuBar> MenuBars
        {
            get { return menuBars; }
            set { menuBars = value; RaisePropertyChanged(); }
        }
    }
     <ListBox x:Name="MenuBar"
             Margin="0,16,0,16"
             AutomationProperties.Name="DemoPagesListBox"
             ItemsSource="{Binding MenuBars}"
             Style="{StaticResource MaterialDesignNavigationPrimaryListBox}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectionChanged">
                <i:InvokeCommandAction Command="{Binding NavigateCommand}"
                                       CommandParameter="{Binding ElementName=MenuBar, Path=SelectedItem}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
        <ListBox.Resources>
            <Style BasedOn="{StaticResource MaterialDesignScrollBarMinimal}"
                   TargetType="ScrollBar" />
        </ListBox.Resources>
        <ListBox.ItemTemplate>
            <DataTemplate DataType="domain:DemoItem">
                <StackPanel Orientation="Horizontal">
                    <materialDesign:PackIcon Width="20"
                                             Height="20"
                                             Margin="10,4,0,4"
                                             Kind="{Binding Icon}" />
                    <TextBlock Margin="10,5,0,4"
                               AutomationProperties.AutomationId="DemoItemPage"
                               FontSize="15"
                               Text="{Binding Title}" />
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

攜帶樣式

    <StackPanel Grid.Row="6" Orientation="Vertical">
        <TextBlock Text="{Binding ElementName=window,Path=Top,StringFormat='Top:{0}'}" />
        <TextBlock>
            <TextBlock.Text>
                <MultiBinding StringFormat="pos:{0},{1}">
                    <Binding ElementName="window"
                             Path="Top" />
                    <Binding ElementName="window"
                             Path="Left" />
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock> 
    </StackPanel>

1.3.3 綁定上下文定義的數據

source綁定x:key的值

    <Window.Resources>
        <sys:String x:Key="name">小明</sys:String>
        <x:Array x:Key="data" Type="sys:String">
            <sys:String>小刚</sys:String>
        </x:Array>
        <local:DataClass Value="这是单个对象作为数据源,INotifyPropertyChanged  " x:Key="dataclass"></local:DataClass>
    </Window.Resources>
    
     <Grid>
            <TextBlock Text="{Binding Source={StaticResource name}}"></TextBlock>
            <TextBlock Text="{Binding Source={StaticResource data},Path=[0]}"></TextBlock>
            <TextBlock Text="{Binding Source={StaticResource dataclass},Path=Value}" /> 
    </Grid>

複雜一點,靜態資源

namespace BlankApp1.ViewModels.Config
{
    public class BindingViewModel
    {
        public string NameMsg { get; } = "Nolan";

        public static string StaticString = "Static String";

        public const string ConstString = "Const String";
    }
}

    <UserControl.Resources>
        <model:BindingViewModel x:Key="demo01" />
    </UserControl.Resources>
    <Grid>
        <StackPanel Grid.Row="1" Orientation="Vertical">
            <TextBlock Text="{Binding}" />
            <TextBlock Text="{Binding Source={StaticResource demo01}, Path=NameMsg}" />
            <TextBlock Text="{Binding Source={x:Static model:BindingViewModel.StaticString}}" />
            <TextBlock Text="{Binding Source={x:Static model:BindingViewModel.ConstString}}" />
        </StackPanel>
    </Grid>

其它的:

  • CollectionViewSource :數字類別進行分組,排序,篩選
  • ObjectDataProvider :把Combox綁定到枚舉類型

1.3.4 Reference

    <Grid Grid.Row="3">
        <StackPanel>
            <TextBlock Text="{Binding source={x:Reference name=xxxx },Path=xxxx}" Margin="8" Height="30"/>
        </StackPanel>
    </Grid>

1.3.5 RelativeSource

在进行Binding的时候,如果能够明确到数据源属性的Path(Name),就可用Source、ElementName进行指定,但是有时候需要绑定的数据源可能没有明确的Path(Name),此时就需要利用Binding对象的RelativeSource属性来进行绑定源属性的指定

參數詳解:

  • AncestorType表示父控件的类型

  • AncestorLevel表示向父控件查找的层级

綁定自己

<TextBlock FontSize="16" Text="{Binding FontSize,RelativeSource={RelativeSource Self}}"/>

綁定當前窗口

這兩種寫法好像是一樣的。

    <StackPanel Margin="15">
        <TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl},Path=Height}" />
        <TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}},Path=Height}" />
    </StackPanel>

綁定父級

     <ListBox x:Name="MenuBar"
             Margin="0,16,0,16"
             AutomationProperties.Name="DemoPagesListBox"
             ItemsSource="{Binding MenuBars}"
             Style="{StaticResource MaterialDesignNavigationPrimaryListBox}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectionChanged">
                <i:InvokeCommandAction Command="{Binding NavigateCommand}"
                                       CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListBox}}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
        <ListBox.Resources>
            <Style BasedOn="{StaticResource MaterialDesignScrollBarMinimal}"
                   TargetType="ScrollBar" />
        </ListBox.Resources>
        <ListBox.ItemTemplate>
            <DataTemplate DataType="domain:DemoItem">
                <StackPanel Orientation="Horizontal">
                    <materialDesign:PackIcon Width="20"
                                             Height="20"
                                             Margin="10,4,0,4"
                                             Kind="{Binding Icon}" />
                    <TextBlock Margin="10,5,0,4"
                               AutomationProperties.AutomationId="DemoItemPage"
                               FontSize="15"
                               Text="{Binding Title}" />
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

綁定父級層級

    <StackPanel Margin="15">
        <Grid Name="DEMO01">
            <Grid Name="DEMO02">
                <Grid Name="DEMO03">
                    <TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid,AncestorLevel=2},Path=Name}" />
                </Grid>
            </Grid>
        </Grid>
    </StackPanel>

綁定模板

    <StackPanel Grid.Row="4">
        <Label Padding="10"
               Content="hello">
            <Label.Template>
                <ControlTemplate TargetType="Label">
                    <Border>
                        <ContentPresenter Margin="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Padding,Mode=OneWay}" />
                        <ContentPresenter Margin="{TemplateBinding Padding}" />
                    </Border>
                </ControlTemplate>
            </Label.Template>
        </Label>
    </StackPanel>

1.3.6 UpdateSourceTrigger

Default是UpdateSourceTrigger的默认值。

其他选项是PropertyChanged, LostFocus和Explicit

  • LostFocus: 元素丢失焦点时,Text属性才会更新。
  • PropertyChanged: 修改後更新。
  • Explicit: 必须手动推送更新,使用Binding上的UpdateSource调用。
    <StackPanel Margin="15">
        <WrapPanel>
            <TextBlock Text="Window title: " />
            <TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
            <Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
        </WrapPanel>
        <WrapPanel Margin="0,10,0,0">
            <TextBlock Text="Window dimensions: " />
            <TextBox Text="{Binding Width, UpdateSourceTrigger=LostFocus}" Width="50" />
            <TextBlock Text=" x " />
            <TextBox Text="{Binding Height, UpdateSourceTrigger=PropertyChanged}" Width="50" />
        </WrapPanel>
    </StackPanel>
namespace WpfTutorialSamples.DataBinding
{
    public partial class DataContextSample : Window
    {
        public DataContextSample()
        {
            InitializeComponent();
            this.DataContext = this;
        }

        private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
        {
            BindingExpression binding = txtWindowTitle.GetBindingExpression(TextBox.TextProperty);
            binding.UpdateSource();
        }
    }
}

1.3.7 TargetNullValue

當返回值為Null時,給一個默認值

    <StackPanel Grid.Row="4">
        <TextBlock Text="{Binding Message,TargetNullValue = Empty}" />
    </StackPanel>

2. Mvvm

ICommand命令主要和按钮作用相关

INotifyPropertyChanged主要用于消息通知,更新界面数据

在項目中添加類,如圖所示:MainViewModel.cs/MyCommand.cs

图片.png

namespace WpfApp01
{
    public class MyCommand : ICommand
    {

        private Action showAction;

        public MyCommand(Action action)
        {
            this.showAction = action;
        }
        
        public event EventHandler CanExecuteChanged;

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            showAction();
        }
    }
}
namespace WpfApp01
{
    public class MainViewModel : ViewModelBase
    {

        private string name;
        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                OnPropertyChanged();
            }
        }

        public MyCommand ShowCommand { get; set; }

        public MainViewModel()
        {
            Name = "Hello Word";
            this.ShowCommand = new MyCommand(ShowMsg);
        }

        public void ShowMsg()
        {
            Name = "弹出消息示例";
            MessageBox.Show("弹出消息");
        }
    }
}
namespace WpfApp01
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged([CallerMemberName] string propertyName = "") 
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

最後在MainWindow.xaml.cs構造函數中導入MainViewModel

        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainViewModel();
        }
        <Grid Grid.Row="4">
            <Grid.RowDefinitions>
                <RowDefinition Height="40"/>
                <RowDefinition Height="80"/>
            </Grid.RowDefinitions>
            <Button Grid.Row="0"  Content="Button" Width="200" Height="30" Command="{Binding ShowCommand}"/>
            <TextBox Grid.Row="1" Text="{Binding Name}" Height="65" Width="350"/>
        </Grid>