昨晚有朋友问:
[quote]Hi,帮我讲解一下WPF怎样在Canvas或者Grid上根据变量改变Shape的位置和形状吧~[/quote]
没太理解问题在哪里,不过看样子是数据绑定方面不熟悉?
那就写个用到Canvas和数据绑定的例子吧。在VS2008里新建一个WPF应用,然后把下面的Window1.xaml和Window1.xaml.cs替换进去就行。
做出来的是像这样的一个界面(是很丑啦 T T)
[img]http://rednaxelafx.iteye.com/upload/picture/pic/28671/38b3805a-a02b-3d73-843a-72c93fb6d6fb.jpg[/img]
把Window里的根容器Grid分成上下两行:上半部分放置用于控制和显示坐标的控件;下半部分放置一个Canvas,里面放一个Rectangle。在TextBox里输入数字或者滑动ScrollBar都能够改变Rectangle的位置。
也就是随便在VS2008的WPF Designer里拖拖控件把界面拉出来:
Window1.xaml
那么来看看这个界面涉及到哪些数据绑定。
数据源方面,Window1里有两个int类型的属性,RectX和RectY,分别用于指定位于Canvas内的Rectangle的X和Y坐标。
[color=red]更新:Window1的DataContext原本在代码里设置为了this,现在改为在XAML里直接设置。[/color]
接下来看看绑定目标方面。首先是TextBox。两个TextBox分别与RectX和RectY做了双向绑定,也就是说当RecX或RectY有了更新,则对应的TextBox会马上反应更新,而用户在TextBox中输入数字的时候RectX或RectY也会得到相应的更新。使用标记扩展(markup extension)语法来指定绑定:
这里要注意的是,如果不显式指定TextBox的Text数据绑定中的UpdateSourceTrigger,则默认为LooseFocus,那么要等到TextBox失去焦点后才会发生TextBox->source的更新;而这里我们想要的是文本框里的文本发生改变时就马上更新。
绑定方向一共有4种:OneWay、TwoWay、OneTime和OneWayToSource。
OneWay就是目标根据数据源变化;
TwoWay就是目标和数据源相互都能更新;
OneTime就是目标只在初始化的时候读取一次数据,以后就不再跟随数据源而变化;
OneWayToSource是OneWay的反向,在目标的数据更新的时候也更新到数据源上。这主要是为了让没有DependencyProperty的属性能被有DependencyProperty的属性更新。
如果不使用标记扩展,也可以用传统的XML语法来指定数据绑定,像这样:
然后是两个Label。它们只是对RectX和RectY做了单向绑定,也就是当RectX或RectY有了更新,则对应的Label会马上反应更新。
由于Label的Content默认就是OneWay的,这里就没有显式指定。
接着,两个ScrollBar。跟TextBox相似,也是做了双向绑定。不过ScrollBar的Value不用显式指定UpdateSourceTrigger也行。
最后是Canvas里的Rectangle。与Label类似,对RectX和RectY做了单向绑定,分别绑定到Canvas.Left和Canvas.Top这两个附加属性上。
OK,到这里为止都是在XAML里设置数据绑定的目标。那数据源的一侧要如何实现呢?关键问题是,当数据源的值发生了变化,应该如何通知数据绑定的目标?
=====================================================================
(不是特别推荐的方法)[b]通过实现INotifyPropertyChanged接口来实现数据源[/b]
WPF能理解INotifyPropertyChanged接口,通过其PropertyChanged事件来得到数据源更新的通知。
实现要点是:
1、实现INotifyPropertyChanged接口,并声明其成员PropertyChanged事件;
2、定义一个OnPropertyChanged()方法来发送上述事件;不一定要叫OnPropertyChanged,这只是习惯;
3、在需要通知更新的属性的setter里调用OnPropertyChanged()。
这种做法在WinForms里应该很常见,因为WinForms的数据绑定支持实在算不上好。WPF里有更方便更强大的支持,也就是DependencyProperty。
=====================================================================
[b]通过DependencyProperty来实现数据源[/b]
DependencyProperty的内部实现机制描述起来觉得好复杂。我也没理解透彻。所以这里就不多说了,详细还是找本WPF的书来看吧。
自己需要写的代码方面则很简单,如下:
Window1.xaml.cs
实现要点是:
前提:数据源继承FrameworkElement。
1、为需要数据绑定的属性声明静态只读的DependencyProperty域;名字按习惯一般是[color=brown]要绑定的属性名+Property[/color]。例如Foo属性的DependencyProperty就叫FooProperty;
2、在静态构造器里通过DependencyProperty上的几个静态工厂方法(Register、RegisterAttached、RegisterAttachedReadOnly、RegisterReadOnly等)来初始化这些DependencyProperty域;
3、在需要做数据绑定的属性的setter里调用继承自DependencyObject类的SetValue()方法,在getter里调用GetValue()方法。WPF的UIElement本身就继承自DependencyObject,所以在它的子类里都可以使用SetValue()和GetValue()。
然后基本的DependencyProperty就设置好可以使用了。需要更精确的配置的话,还可以通过FrameworkPropertyMetadata之类的数据来指定默认的绑定方向、默认值等一系列属性。
嘛,基本上就这样吧~懒得复制粘贴的话直接用附件里的Solution也行。
顺便一提,那两个按钮我只是想测试一下数据源是否确实被更新了而已。实际上没啥用,可以忽略……
[quote]Hi,帮我讲解一下WPF怎样在Canvas或者Grid上根据变量改变Shape的位置和形状吧~[/quote]
没太理解问题在哪里,不过看样子是数据绑定方面不熟悉?
那就写个用到Canvas和数据绑定的例子吧。在VS2008里新建一个WPF应用,然后把下面的Window1.xaml和Window1.xaml.cs替换进去就行。
做出来的是像这样的一个界面(是很丑啦 T T)
[img]http://rednaxelafx.iteye.com/upload/picture/pic/28671/38b3805a-a02b-3d73-843a-72c93fb6d6fb.jpg[/img]
把Window里的根容器Grid分成上下两行:上半部分放置用于控制和显示坐标的控件;下半部分放置一个Canvas,里面放一个Rectangle。在TextBox里输入数字或者滑动ScrollBar都能够改变Rectangle的位置。
也就是随便在VS2008的WPF Designer里拖拖控件把界面拉出来:
Window1.xaml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="TestWpfCanvasShapeDataBinding.Window1"
xmlns:Custom="http://schemas.microsoft.com/winfx/2006/xaml/composite-font"
x:Name="mainWindow"
DataContext="{Binding ElementName=mainWindow}"
Title="Test Data Binding" Height="480" Width="230" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Name="txtX" Margin="12,10,0,0" Height="23" Width="95"
VerticalAlignment="Top" HorizontalAlignment="Left"
Text="{Binding Path=RectX, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Label Name="lblX" Margin="12,40,0,0" Height="23" Width="95"
VerticalAlignment="Top" HorizontalAlignment="Left"
Content="{Binding Path=RectX}" />
<Button Name="btnX" Margin="0,10,5,0" Height="23" Width="81"
VerticalAlignment="Top" HorizontalAlignment="Right"
Click="button1_Click" >
Check X Value
</Button>
<ScrollBar Name="scbX" Margin="12,70,5,0" Height="20" Width="181"
VerticalAlignment="Top" Orientation="Horizontal"
Maximum="200" Value="{Binding Path=RectX, Mode=TwoWay}" />
<TextBox Name="txtY" Margin="12,120,0,0" Height="23" Width="95"
VerticalAlignment="Top" HorizontalAlignment="Left"
Text="{Binding Path=RectY, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Label Name="lblY" Margin="12,150,0,0" Height="23" Width="95"
VerticalAlignment="Top" HorizontalAlignment="Left"
Content="{Binding Path=RectY}" />
<Button Name="btnY" Margin="0,120,5,0" Height="23" Width="81"
VerticalAlignment="Top" HorizontalAlignment="Right"
Click="button2_Click" >
Check Y Value
</Button>
<ScrollBar Name="scbY" Margin="12,180,5,0" Height="20" Width="181"
VerticalAlignment="Top" Orientation="Horizontal"
Value="{Binding Path=RectY, Mode=TwoWay}" Maximum="200" />
<Canvas Margin="0,0,0,0" Grid.Row="1" >
<Canvas.Background>
<Custom:LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5" >
<Custom:GradientStop Color="#FF337496" Offset="0" />
<Custom:GradientStop Color="#FF94E2EC" Offset="1" />
</Custom:LinearGradientBrush>
</Canvas.Background>
<Rectangle Height="20" Width="20" Stroke="#FF301A87" Fill="#FF8169E6"
Canvas.Left="{Binding Path=RectX}"
Canvas.Top="{Binding Path=RectY}" />
</Canvas>
</Grid>
</Window>那么来看看这个界面涉及到哪些数据绑定。
数据源方面,Window1里有两个int类型的属性,RectX和RectY,分别用于指定位于Canvas内的Rectangle的X和Y坐标。
[color=red]更新:Window1的DataContext原本在代码里设置为了this,现在改为在XAML里直接设置。[/color]
接下来看看绑定目标方面。首先是TextBox。两个TextBox分别与RectX和RectY做了双向绑定,也就是说当RecX或RectY有了更新,则对应的TextBox会马上反应更新,而用户在TextBox中输入数字的时候RectX或RectY也会得到相应的更新。使用标记扩展(markup extension)语法来指定绑定:
<TextBox Name="txtX"
Text="{Binding Path=RectX, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />这里要注意的是,如果不显式指定TextBox的Text数据绑定中的UpdateSourceTrigger,则默认为LooseFocus,那么要等到TextBox失去焦点后才会发生TextBox->source的更新;而这里我们想要的是文本框里的文本发生改变时就马上更新。
绑定方向一共有4种:OneWay、TwoWay、OneTime和OneWayToSource。
OneWay就是目标根据数据源变化;
TwoWay就是目标和数据源相互都能更新;
OneTime就是目标只在初始化的时候读取一次数据,以后就不再跟随数据源而变化;
OneWayToSource是OneWay的反向,在目标的数据更新的时候也更新到数据源上。这主要是为了让没有DependencyProperty的属性能被有DependencyProperty的属性更新。
如果不使用标记扩展,也可以用传统的XML语法来指定数据绑定,像这样:
<TextBox Name="txtX" >
<TextBox.Text>
<Binding Path="RectX" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" />
</TextBox.Text>
</TextBox>然后是两个Label。它们只是对RectX和RectY做了单向绑定,也就是当RectX或RectY有了更新,则对应的Label会马上反应更新。
<Label Name="lblX"
Content="{Binding Path=RectX}" />由于Label的Content默认就是OneWay的,这里就没有显式指定。
接着,两个ScrollBar。跟TextBox相似,也是做了双向绑定。不过ScrollBar的Value不用显式指定UpdateSourceTrigger也行。
<ScrollBar Name="scbX"
Value="{Binding Path=RectX, Mode=TwoWay}" />最后是Canvas里的Rectangle。与Label类似,对RectX和RectY做了单向绑定,分别绑定到Canvas.Left和Canvas.Top这两个附加属性上。
<Rectangle
Canvas.Left="{Binding Path=RectX}"
Canvas.Top="{Binding Path=RectY}"/>OK,到这里为止都是在XAML里设置数据绑定的目标。那数据源的一侧要如何实现呢?关键问题是,当数据源的值发生了变化,应该如何通知数据绑定的目标?
=====================================================================
(不是特别推荐的方法)[b]通过实现INotifyPropertyChanged接口来实现数据源[/b]
WPF能理解INotifyPropertyChanged接口,通过其PropertyChanged事件来得到数据源更新的通知。
using System.ComponentModel;
using System.Windows;
namespace TestWpfCanvasShapeDataBinding {
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window, INotifyPropertyChanged {
private int _rectX;
private int _rectY;
public int RectX {
get { return _rectX; }
set {
_rectX = value;
OnPropertyChanged( "RectX" );
}
}
public int RectY {
get { return _rectY; }
set {
_rectY = value;
OnPropertyChanged( "RectY" );
}
}
public Window1( ) {
InitializeComponent( );
//this.DataContext = this;
}
private void button1_Click( object sender, RoutedEventArgs e ) {
MessageBox.Show( this.RectX.ToString( ) );
}
private void button2_Click( object sender, RoutedEventArgs e ) {
MessageBox.Show( this.RectY.ToString( ) );
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged( string propertyName ) {
var handler = PropertyChanged;
if ( null != handler ) {
handler( this, new PropertyChangedEventArgs( propertyName ) );
}
}
#endregion
}
}实现要点是:
1、实现INotifyPropertyChanged接口,并声明其成员PropertyChanged事件;
2、定义一个OnPropertyChanged()方法来发送上述事件;不一定要叫OnPropertyChanged,这只是习惯;
3、在需要通知更新的属性的setter里调用OnPropertyChanged()。
这种做法在WinForms里应该很常见,因为WinForms的数据绑定支持实在算不上好。WPF里有更方便更强大的支持,也就是DependencyProperty。
=====================================================================
[b]通过DependencyProperty来实现数据源[/b]
DependencyProperty的内部实现机制描述起来觉得好复杂。我也没理解透彻。所以这里就不多说了,详细还是找本WPF的书来看吧。
自己需要写的代码方面则很简单,如下:
Window1.xaml.cs
using System.Windows;
namespace TestWpfCanvasShapeDataBinding {
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window {
public static readonly DependencyProperty RectXProperty;
public static readonly DependencyProperty RectYProperty;
public int RectX {
get { return ( int ) GetValue( RectXProperty ); }
set { SetValue( RectXProperty, value ); }
}
public int RectY {
get { return ( int ) GetValue( RectYProperty ); }
set { SetValue( RectYProperty, value ); }
}
static Window1( ) {
RectXProperty = DependencyProperty.Register( "RectX", typeof( int ), typeof( Window1 ) );
RectYProperty = DependencyProperty.Register( "RectY", typeof( int ), typeof( Window1 ) );
}
public Window1( ) {
InitializeComponent( );
}
private void button1_Click( object sender, RoutedEventArgs e ) {
MessageBox.Show( this.RectX.ToString( ) );
}
private void button2_Click( object sender, RoutedEventArgs e ) {
MessageBox.Show( this.RectY.ToString( ) );
}
}
}实现要点是:
前提:数据源继承FrameworkElement。
1、为需要数据绑定的属性声明静态只读的DependencyProperty域;名字按习惯一般是[color=brown]要绑定的属性名+Property[/color]。例如Foo属性的DependencyProperty就叫FooProperty;
2、在静态构造器里通过DependencyProperty上的几个静态工厂方法(Register、RegisterAttached、RegisterAttachedReadOnly、RegisterReadOnly等)来初始化这些DependencyProperty域;
3、在需要做数据绑定的属性的setter里调用继承自DependencyObject类的SetValue()方法,在getter里调用GetValue()方法。WPF的UIElement本身就继承自DependencyObject,所以在它的子类里都可以使用SetValue()和GetValue()。
然后基本的DependencyProperty就设置好可以使用了。需要更精确的配置的话,还可以通过FrameworkPropertyMetadata之类的数据来指定默认的绑定方向、默认值等一系列属性。
嘛,基本上就这样吧~懒得复制粘贴的话直接用附件里的Solution也行。
顺便一提,那两个按钮我只是想测试一下数据源是否确实被更新了而已。实际上没啥用,可以忽略……
本文介绍如何在WPF应用程序中使用Canvas和Grid布局,通过数据绑定改变形状的位置。利用TextBox、ScrollBar和Label控件调整形状位置并实时更新。
379

被折叠的 条评论
为什么被折叠?



