变更通知是WPF的一个精髓,它使得MVVM成为WPF的标准架构!在数据绑定中,除了正常的数据模版绑定,还会涉及到模板内控件的事件绑定,以及对parent内容的绑定!接下来的示例将会展示大部分常用的绑定场景。
示例实现的是一个便签软件,主要功能有新增、删除、修改、切换日期、读取便签列表、设置是否完成。
下面先说下几种绑定方式:
继承于ICommand和INotifyPropertyChanged的事件委托和通知变更类这里就不写了,网上很多的!或者你可以直接使用mvvmlight的框架。
在App.xaml中加入资源NoteViewModel,方便在blend中绑定
<Application x:Class="ManageNote.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:ManageNote.ViewModel" Startup="ApplicationStartup" > <Application.Resources> <local:NoteViewModel x:Key="NoteViewModel"></local:NoteViewModel> </Application.Resources> </Application>打开blend,如果不习惯用blend,可以参考后面的xaml代码自己编写!
1、模板数据绑定,这是最基础的绑定,通过blend可以实现
1.1 绑定window的datacontext
1.2绑定listbox的itemsource
1.3、右键Listbox > 编辑其他模版 > 编辑生成的项 > 创建新项,拖入一个checkbox和textblock,并绑定相对应的字段。如果需要字段转换,最常用的是bool和visible等,可以自己写convert转换器,具体写法自行百度。
1.4 通过“行为”来控制,文本框是否可编辑!原本是只读模式,在双击后可以进行编辑,只展示一个示例,其他的在代码中找!
2、模版父控件的绑定
下面需要绑定checkbox的点击事件来设置该项任务是否完成。在blend中可以看到,在模板的数据绑定中只能显示notemodel中的字段,这是因为item的datacontext类型为NoteModel。我们想绑定NoteViewModel中的CompleteCommand事件,就需要自行指定绑定的数据源。这需要通过代码实现:
<CheckBox x:Name="checkBox" d:LayoutOverrides="Width, Height" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding IsCompleted}" Command="{Binding DataContext.CompleteCommand, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent}}" >
msdn的RelativeSource:http://msdn.microsoft.com/zh-cn/library/system.windows.data.binding.source(v=vs.90).aspx
RelativeSource成员:http://msdn.microsoft.com/zh-SG/library/system.windows.data.relativesource_members(v=vs.85)
RelativeSource的AncestorType:http://msdn.microsoft.com/zh-SG/library/system.windows.data.relativesource.ancestortype(v=vs.85)
这几篇文章读完就能掌握常用的几种绑定方式了。
3、模板绑定某一元素的属性(通过RelativeSource查找)
<ContextMenu x:Key="ContextMenuKey"> <MenuItem Header="删除" Command="{Binding DataContext.DelPlan, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1,AncestorType=ItemsPresenter}}" /> </ContextMenu>AncestorLevel来确定向上查找的级别,AncestorType确定要查找的元素!这点很类似于JQuery的Selector。
以上就是几种常用的绑定方式。
下面贴上部分源码:
NoteWindow.xaml
<Window 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:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero" xmlns:ManageNote_ViewModel="clr-namespace:ManageNote.ViewModel" x:Class="ManageNote.NoteWindow" Title="便签" RenderOptions.BitmapScalingMode="NearestNeighbor" mc:Ignorable="d" ShowInTaskbar="False" WindowStyle="None" AllowsTransparency="True" ResizeMode="NoResize" Background="{x:Null}" VerticalContentAlignment="Stretch" Width="250" Height="200" DataContext="{DynamicResource NoteViewModel}" > <Window.Resources> <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/> <ContextMenu x:Key="ContextMenuKey"> <MenuItem Header="删除" Command="{Binding DataContext.DelPlan, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1,AncestorType=ItemsPresenter}}" /> </ContextMenu> <Style x:Key="ButtonStyle1" TargetType="{x:Type Button}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Grid> <Path Data="M16,16 L32,8.0002261 32,24.000226 z" Fill="#FF888864" Stretch="Fill" Stroke="Black" StrokeThickness="0"/> <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Content=""/> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsFocused" Value="True"/> <Trigger Property="IsDefaulted" Value="True"/> <Trigger Property="IsMouseOver" Value="True"/> <Trigger Property="IsPressed" Value="True"/> <Trigger Property="IsEnabled" Value="False"/> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <DataTemplate x:Key="DataTemplate1"> <Grid d:DesignWidth="238" Margin="0,2" Background="{x:Null}"> <Grid.ColumnDefinitions> <ColumnDefinition Width="25"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <CheckBox x:Name="checkBox" d:LayoutOverrides="Width, Height" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding IsCompleted}" Command="{Binding DataContext.CompleteCommand, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent}}" > </CheckBox> <TextBox x:Name="textBox" Grid.Column="1" Height="Auto" TextWrapping="Wrap" Text="{Binding Content}" IsReadOnly="True" Background="{x:Null}" BorderBrush="{x:Null}" SelectionBrush="#FF0D69FF" BorderThickness="1" Style="{DynamicResource TextBoxStyle1}"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseDoubleClick"> <ei:ChangePropertyAction TargetObject="{Binding ElementName=textBox}" PropertyName="IsReadOnly" Value="False"/> </i:EventTrigger> <i:EventTrigger EventName="LostFocus"> <ei:ChangePropertyAction TargetObject="{Binding ElementName=textBox}" PropertyName="IsReadOnly" Value="True"/> <i:InvokeCommandAction Command="{Binding DataContext.UpdateTaskCommand,RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding Path=Content,RelativeSource={ RelativeSource Mode=TemplatedParent}}"/> </i:EventTrigger> </i:Interaction.Triggers> </TextBox> </Grid> </DataTemplate> <Style x:Key="ListBoxItemStyle1" TargetType="{x:Type ListBoxItem}"> <Setter Property="Background" Value="Transparent"/> <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/> <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/> <Setter Property="Padding" Value="2,0,0,0"/> <Setter Property="ContextMenu" Value="{StaticResource ContextMenuKey}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <Border x:Name="Bd" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true" Background="{x:Null}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal"/> <VisualState x:Name="MouseOver"/> <VisualState x:Name="Disabled"/> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> <LinearGradientBrush x:Key="TextBoxBorder" EndPoint="0,20" MappingMode="Absolute" StartPoint="0,0"> <GradientStop Color="#ABADB3" Offset="0.05"/> <GradientStop Color="#E2E3EA" Offset="0.07"/> <GradientStop Color="#E3E9EF" Offset="1"/> </LinearGradientBrush> <Style x:Key="TextBoxStyle1" BasedOn="{x:Null}" TargetType="{x:Type TextBox}"> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/> <Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Padding" Value="1"/> <Setter Property="AllowDrop" Value="true"/> <Setter Property="FocusVisualStyle" Value="{x:Null}"/> <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/> <Setter Property="Stylus.IsFlicksEnabled" Value="False"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TextBox}"> <Microsoft_Windows_Themes:ListBoxChrome x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderFocused="{TemplateBinding IsKeyboardFocusWithin}" SnapsToDevicePixels="true"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal"/> <VisualState x:Name="Disabled"/> <VisualState x:Name="ReadOnly"/> <VisualState x:Name="MouseOver"/> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" BorderThickness="1"/> </Microsoft_Windows_Themes:ListBoxChrome> <ControlTemplate.Triggers> <Trigger Property="IsEnabled" Value="false"> <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources> <Grid Margin="3"> <Border CornerRadius="5" Background="Black" BorderThickness="1" BorderBrush="#FFCDCDCD" > <Border.Effect> <DropShadowEffect BlurRadius="9" Color="#FF4B4747" Opacity="0.845" Direction="285" ShadowDepth="1"/> </Border.Effect> </Border> <Grid> <Grid.Background> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="#FFFDFDCB" Offset="0"/> <GradientStop Color="#FFFCF9A1" Offset="1"/> </LinearGradientBrush> </Grid.Background> <Grid.RowDefinitions> <RowDefinition Height="30" /> <RowDefinition /> </Grid.RowDefinitions> <Border BorderBrush="Black" BorderThickness="0" Margin="0" Background="#FFF8F7B6"/> <Image x:Name="image" Height="20" Source="5_content_new.png" Stretch="Fill" Width="20" HorizontalAlignment="Right" Margin="0,0,30,0" VerticalAlignment="Center" > <i:Interaction.Triggers> <i:EventTrigger EventName="MouseLeftButtonDown" SourceObject="{Binding ElementName=image}"> <ei:CallMethodAction TargetObject="{Binding Mode=OneWay}" MethodName="AddAgenda" /> </i:EventTrigger> </i:Interaction.Triggers> </Image> <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="labDate" Foreground="#FF646363" FontWeight="Bold" FontSize="13.333" Text="{Binding Title}" /> <Button x:Name="btnPre" Content="Button" HorizontalAlignment="Left" Margin="5,0,0,0" Style="{DynamicResource ButtonStyle1}" Width="16" Height="16" VerticalAlignment="Center" Command="{Binding ChangeDateCommand, Mode=OneWay}" CommandParameter="-1"/> <Button x:Name="btnNext" Content="Button" Margin="0,0,5,0" Style="{DynamicResource ButtonStyle1}" RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Right" Width="16" Height="16" VerticalAlignment="Center" Background="{x:Null}" Command="{Binding ChangeDateCommand, Mode=OneWay}" CommandParameter="1"> <Button.RenderTransform> <TransformGroup> <ScaleTransform/> <SkewTransform/> <RotateTransform Angle="180"/> <TranslateTransform/> </TransformGroup> </Button.RenderTransform> </Button> <ListBox Margin="5" Grid.Row="1" Background="{x:Null}" BorderThickness="0" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ItemContainerStyle="{DynamicResource ListBoxItemStyle1}" ScrollViewer.VerticalScrollBarVisibility="Hidden" ItemTemplate="{DynamicResource DataTemplate1}" ItemsSource="{Binding Tasks}" HorizontalContentAlignment="Stretch"> </ListBox> </Grid> </Grid> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Text; using ManageNote.Model; using System.Data; using MySql.Data.MySqlClient; using System.Collections.ObjectModel; using System.Windows; using System.Windows.Input; namespace ManageNote.ViewModel { public class NoteViewModel : NotificationObject { DateTime currentDate; private DelegateCommand<NoteModel> completeCommand; /// <summary> /// 设置完成状态事件 /// </summary> public DelegateCommand<NoteModel> CompleteCommand { get { if (this.completeCommand == null) { this.completeCommand = new DelegateCommand<NoteModel>(this.SetComplete); } return this.completeCommand; } } private DelegateCommand<NoteModel> delPlan; /// <summary> /// 删除计划事件 /// </summary> public DelegateCommand<NoteModel> DelPlan { get { if (this.delPlan == null) { this.delPlan = new DelegateCommand<NoteModel>(this.Delete); } return this.delPlan; } } private DelegateCommand<object> changeDateCommand; /// <summary> /// 修改日期事件 /// </summary> public DelegateCommand<object> ChangeDateCommand { get { if (this.changeDateCommand == null) { this.changeDateCommand = new DelegateCommand<object>(this.ChangeDate); } return this.changeDateCommand; } } private DelegateCommand<NoteModel> updateTaskCommand; /// <summary> /// 修改事件 /// </summary> public DelegateCommand<NoteModel> UpdateTaskCommand { get { if (this.updateTaskCommand == null) { this.updateTaskCommand = new DelegateCommand<NoteModel>(this.UpdateAgenda); } return this.updateTaskCommand; } } public NoteViewModel() { this.BindDate(DateTime.Now.Date); } private string title; /// <summary> /// 标题 /// </summary> public string Title { get { return this.title; } set { if (this.title != value) { this.title = value; this.RaisePropertyChanged("Title"); } } } private ObservableCollection<NoteModel> tasks = null; /// <summary> /// 任务计划集合 /// </summary> public ObservableCollection<NoteModel> Tasks { get { return this.tasks; } set { if (this.tasks != value) { this.tasks = value; this.RaisePropertyChanged("Tasks"); } } } /// <summary> /// 设置完成状态 /// </summary> /// <param name="iComplete"></param> /// <returns></returns> private void SetComplete(NoteModel iModel) { StringBuilder strSql = new StringBuilder(); strSql.Append("UPDATE m_agenda SET IsComplete="); strSql.Append(iModel.IsCompleted ? "1" : "0"); strSql.Append(" WHERE Id="); strSql.Append(iModel.Id); EJiang.Common.DirectDbHelperMysql.ExecuteSql(strSql.ToString()); } private void ChangeDate(object days) { this.BindDate(this.currentDate.AddDays(Convert.ToInt32(days))); } /// <summary> /// 用于外部调用,刷新信息 /// </summary> public void RefrushInfo() { this.Tasks = this.GetNoteByDate(SystemConfig.userId, this.currentDate); } /// <summary> /// 绑定信息 /// </summary> /// <param name="date"></param> private void BindDate(DateTime date) { this.currentDate = date; this.Title = this.currentDate.ToString("yyyy年MM月dd"); this.Tasks = this.GetNoteByDate(SystemConfig.userId, this.currentDate); } /// <summary> /// 删除 /// </summary> /// <param name="id"></param> private void Delete(NoteModel model) { if (MessageBox.Show("确定要删除该条任务?", "删除确认", MessageBoxButton.YesNo) == MessageBoxResult.Yes) { string strSql = "DELETE FROM m_agenda WHERE Id=" + model.Id; if (EJiang.Common.DirectDbHelperMysql.ExecuteSql(strSql) > 0) { this.Tasks.Remove(model); } } } /// <summary> /// 获取日程 /// </summary> /// <param name="userId"></param> /// <param name="date"></param> /// <returns></returns> public ObservableCollection<NoteModel> GetNoteByDate(int userId, DateTime date) { StringBuilder strSql = new StringBuilder(); strSql.Append("SELECT "); strSql.Append("Id, "); strSql.Append("AgendaContent, "); strSql.Append("StartDate, "); strSql.Append("IsComplete "); strSql.Append("FROM m_agenda "); strSql.Append("WHERE StartDate = @startDate "); strSql.Append("AND UserId = @userId "); MySqlParameter[] para = { new MySqlParameter("@userId", userId), new MySqlParameter("@startDate", date) }; ObservableCollection<NoteModel> models = new ObservableCollection<NoteModel>(); DataTable dt = EJiang.Common.DirectDbHelperMysql.Query(strSql.ToString(), para).Tables[0]; foreach (DataRow dr in dt.Rows) { models.Add(new NoteModel { Id = Convert.ToInt32(dr["Id"]), Content = dr["AgendaContent"].ToString(), IsCompleted = dr["IsComplete"].ToString() == "1" ? true : false, StartDate = Convert.ToDateTime(dr["StartDate"]) }); } return models; } /// <summary> /// 新增日程 /// </summary> /// <param name="noteModel"></param> public void AddAgenda() { StringBuilder strSql = new StringBuilder(); strSql.Append("INSERT INTO m_agenda "); strSql.Append("(UserId,"); strSql.Append("AgendaContent, "); strSql.Append("StartDate, "); strSql.Append("LimitDate) "); strSql.Append("VALUES "); strSql.Append("(@UserId, "); strSql.Append("@AgendaContent, "); strSql.Append("@StartDate, "); strSql.Append("@LimitDate)"); MySqlParameter[] para = { new MySqlParameter("@UserId", SystemConfig.userId), new MySqlParameter("@AgendaContent", "请输入您的任务!"), new MySqlParameter("@StartDate", this.currentDate), new MySqlParameter("@LimitDate", this.currentDate) }; int id = Convert.ToInt32(EJiang.Common.DirectDbHelperMysql.ExecuteInsert(strSql.ToString(), para)); if (id > 0) { this.Tasks.Add(new NoteModel { Id = id, Content = "请输入您的任务!", IsCompleted = false, StartDate = this.currentDate }); } } /// <summary> /// 更新日程 /// </summary> /// <param name="noteModel"></param> private void UpdateAgenda(NoteModel noteModel) { StringBuilder strSql = new StringBuilder(); strSql.Append("UPDATE m_agenda "); strSql.Append("SET "); strSql.Append("AgendaContent = @AgendaContent "); strSql.Append("WHERE "); strSql.Append("Id = @Id "); MySqlParameter[] para = { new MySqlParameter("@Id", noteModel.Id), new MySqlParameter("@AgendaContent", noteModel.Content) }; EJiang.Common.DirectDbHelperMysql.ExecuteSql(strSql.ToString(), para); } } }