变更通知是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}}" >
这里的Binding使用到了Path和RelativeSource。
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>
NoteViewModel.cs
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);
}
}
}
源码地址: http://download.youkuaiyun.com/detail/wuwo333/6316627