【6月日新计划09】WPF入门-Binding和簡單的MVVM
1. Binding
顧名思義,就是將獲取的數據和UI上的控件綁定起來,利用數據的變化來更新操作界面的內容
1.1 Target
綁定目標很好理解,融合控件都可以添加綁定屬性,如Button,ListBox。
1.2 Mode
-
通过 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
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>