`
RednaxelaFX
  • 浏览: 3053423 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

在Canvas上根据变量改变Shape的位置?

    博客分类:
  • WPF
阅读更多
昨晚有朋友问:
引用
Hi,帮我讲解一下WPF怎样在Canvas或者Grid上根据变量改变Shape的位置和形状吧~

没太理解问题在哪里,不过看样子是数据绑定方面不熟悉?
那就写个用到Canvas和数据绑定的例子吧。在VS2008里新建一个WPF应用,然后把下面的Window1.xaml和Window1.xaml.cs替换进去就行。

做出来的是像这样的一个界面(是很丑啦 T T)

把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坐标。
更新:Window1的DataContext原本在代码里设置为了this,现在改为在XAML里直接设置。

接下来看看绑定目标方面。首先是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里设置数据绑定的目标。那数据源的一侧要如何实现呢?关键问题是,当数据源的值发生了变化,应该如何通知数据绑定的目标?

=====================================================================

(不是特别推荐的方法)通过实现INotifyPropertyChanged接口来实现数据源

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。

=====================================================================

通过DependencyProperty来实现数据源

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域;名字按习惯一般是要绑定的属性名+Property。例如Foo属性的DependencyProperty就叫FooProperty;
2、在静态构造器里通过DependencyProperty上的几个静态工厂方法(Register、RegisterAttached、RegisterAttachedReadOnly、RegisterReadOnly等)来初始化这些DependencyProperty域;
3、在需要做数据绑定的属性的setter里调用继承自DependencyObject类的SetValue()方法,在getter里调用GetValue()方法。WPF的UIElement本身就继承自DependencyObject,所以在它的子类里都可以使用SetValue()和GetValue()。
然后基本的DependencyProperty就设置好可以使用了。需要更精确的配置的话,还可以通过FrameworkPropertyMetadata之类的数据来指定默认的绑定方向、默认值等一系列属性。

嘛,基本上就这样吧~懒得复制粘贴的话直接用附件里的Solution也行。
顺便一提,那两个按钮我只是想测试一下数据源是否确实被更新了而已。实际上没啥用,可以忽略……
分享到:
评论
4 楼 RednaxelaFX 2009-01-01  
cajon 写道
过不久,项目中就要使用WPF了,我也要开始学习了。大家一起研究啦。

之前我在写那个用来查看.NET的类型层次的小工具的时候本来是想用WPF而不是WinForms来写的。但那个程序要用到Tree,而我对Model-View-ViewModel模式的熟悉程度太差了,没试出好的写法,所以还是暂时放下了,换回用相对熟悉些WinForms来写。

昨晚跟那朋友联系了,结果问题是:
引用
谢了哈,问题解决了。还记得上次问你的random movement algorithm吗,就是想做这种“动画”。我用DispatcherTimer搞定了

泪目,没“猜”对问题 T T

对了,祝大家新年快乐~
3 楼 cajon 2009-01-01  
RednaxelaFX 写道

结论是我还不会WPF啦。充其量也就只能做点像这样无聊的sample。Colin大有空多支几招来~~呵呵 &lt;(_ _)>

呵呵,WPF倒是关注的很早,但是,后来项目一忙也就忘得差不多了。你说了那么多概念,我还真是一个都不熟悉。昨天看到你在写,一时兴起,就写了一点。其中还有一些错误。比如:把AttachedProperty写成了AttachmentProperty。
过不久,项目中就要使用WPF了,我也要开始学习了。大家一起研究啦。
2 楼 RednaxelaFX 2008-12-31  
cajon 写道
我以为这个人问你这个问题的原因是因为他在Rectangle上找不到Left和Top属性的原因吧。

嗯,有道理,多谢Colin老大~这个细节我没想到,确实值得说明。昨天他问了问题就下线了,我也不知道具体是哪里有问题……
属性里Canvas.Left和Canvas.Top等附加属性,是attached properties没错。这种DependencyProperty通过DependencyProperty.RegisterAttached()来注册,使用的时候两种写法是等价的:
Canvas.SetLeft(rect, x);

rect.SetValue(Canvas.LeftProperty, x);

WPF我也是初学啦,整体上都还没用顺手。布局应该遵循怎样的guideline才比较整洁也没底;M-V-VM模式现在还迷糊着,如何通过M-V-VM模式来使用Tree也还没弄清楚;Adorner也是明明很重要但还不了解……T T
结论是我还不会WPF啦。充其量也就只能做点像这样无聊的sample。Colin大有空多支几招来~~呵呵 <(_ _)>
1 楼 cajon 2008-12-31  
哈哈,没想到FX对WPF也有研究。

我以为这个人问你这个问题的原因是因为他在Rectangle上找不到Left和Top属性的原因吧。我也来解释两句。

WPF中,认为一个对象不应该关心它的位置,位置是由该对象的容器来决定的。所以,在Rectangle上找不到Location的信息。这一点和WinForm不一样。

而在XAML中看到的Left和Top属性实际上是一个附加属性(好像英文是:AttachmentProperty).在XAML中需要写成Canvas.Left和Canvas.Top。这样的写法实际上就是在使用Canvas.SetLeft(rect, x)和Canvas.SetTop(rect. y)两个静态方法。这两个静态方法会将属性的值存储到PropertyStorage中。

之所以设计成这样,就是我前面所说的,其实Rectangle并不关心它的位置,而Canvas需要通过这两个属性决定Rectangle的位置,因此,这两个方法定义在Canvase上面。

同样的,对于Grid中的内容,就要通过Grid.SetRow Grid.SetCol和Grid.SetRowSpan等着几个方法了。

相关推荐

    js使用canvas实现画roi功能,并实现交集并集差集操作支持圆形,矩形,旋转矩形绘制,鼠标像素拾取,图片缩放,图片拖拽,像素

    在JavaScript的世界里,Canvas是一个强大的绘图API,它允许开发者在网页上动态绘制图形。本项目专注于使用Canvas实现ROI(感兴趣区域)的功能,包括绘制、交互以及几何形状的集合运算。下面将详细介绍这个项目的各个...

    js对象碰撞检测1

    `canvas`变量引用HTML中的Canvas元素,`context`是用于在Canvas上绘制的2D渲染环境。`stage`是CasualJS的舞台对象,它管理舞台上所有可视对象。 在`start`函数中,创建了三个Bird对象(`bird1`, `bird2`, `bird3`)...

    安卓手绘图片处理画板相关-D-一个关于Drawer绘图的小例子根据时间变化图案也会随之发生变化图案自动生成.rar

    在这个例子中,开发者可能使用了`Handler`或`Runnable`来定时更新图案,这样可以根据时间的变化动态改变绘制的内容。`Handler`通常用于在主线程中执行延迟任务,而`Runnable`是可运行的任务接口,两者结合可以实现...

    android矩形绘画

    在Android中,可以使用`ValueAnimator`或`ObjectAnimator`为矩形添加动画效果,比如改变矩形的大小、位置或颜色。这为用户界面增加了交互性和动态性。 8. 绘制性能优化 当大量绘制时,需要注意性能优化。避免不必...

    网页右上角 Flash 卷边效果代码.zip

    2. **图形绘制**:在Flash环境中,开发者可以使用Shape对象或Sprite对象来绘制图形,比如创建卷边的矩形或曲线,通过改变其位置、旋转角度和透明度来实现动态效果。 3. **时间轴控制**:Flash的时间轴允许开发者...

    爱心飞舞动画效果,冒泡效果

    在编程中,我们可能使用Canvas API(如HTML5中的Canvas)或者图形库(如Unity3D中的Shape GameObjects)来绘制爱心。爱心的样式定制包括颜色、大小和形状,这些可以通过调整参数或使用纹理贴图来实现。 其次,动画...

    俄罗斯方块 J2ME 程序

    - 根据用户的按键改变方块的方向或位置。 #### 四、图形界面绘制 1. **绘制游戏界面**: - 使用 `Graphics` 类提供的方法在屏幕上绘制方块。 - 根据 `screen` 数组的状态来决定每个位置是否应该被绘制为方块。 ...

    HTML5+P5.js实现的羽化线状物动画效果源码.zip

    P5.js库提供了许多方便的函数和方法,使得在Canvas上绘制线状物变得简单。例如,`line()`函数用于在两点之间画线,而`beginShape()`和`endShape()`则可以用来创建多边形或自定义形状。`ellipse()`和`rect()`函数用于...

    java 俄罗斯方块 代码

    通过`Start`变量控制游戏是否开始,`Speed`变量控制游戏速度,`shape`变量表示当前方块的形状,`change`用于记录方块的旋转信息,而`posx`和`posy`则表示方块在游戏区域的位置。 ### 2. 游戏界面与绘图 `MyView`...

    仿ios的SwitchButton 非图片实现.rar

    这可以通过在滑动过程中不断改变滑块的位置来实现,每次改变后都调用`postInvalidate()`异步更新视图。也可以使用`ObjectAnimator`来平滑地过渡到新位置。 5. **颜色和样式**: 为了达到与iOS相似的视觉效果,你需要...

    JavaScript面向对象

    canvas.shapes.forEach(shape =&gt; shape.draw(ctx)); ``` 以上案例展示了如何在JavaScript中使用面向对象的方法来组织代码。通过这种方式,我们可以更容易地管理和扩展代码库。同时,这种方法也提高了代码的可读性...

    基于Android的俄罗斯方块的设计与实现.pdf

    根据给定的文件片段,我们可以提取出关于开发基于Android平台的俄罗斯方块游戏的设计和实现的知识点。以下是从文件片段中提取的关键内容,尽量保持了知识点的完整性和连贯性。 首先,文件的标题是“基于Android的...

    zrender开发的前端接水果的小游戏

    ZRender是阿里巴巴AlloyTeam团队推出的一个2D渲染引擎,它为开发者提供了一套全面的API,使得在Canvas上进行图形绘制变得简单易行。 **一、ZRender简介** ZRender是一个独立的2D渲染引擎,它主要基于HTML5的Canvas...

    Shape-Tracker

    Shape-Tracker项目可能是为了帮助开发者在浏览器环境中实现图形检测、追踪和分析功能。 该项目的核心可能包含以下几个关键知识点: 1. **JavaScript基础**:作为项目的基础,JavaScript是Web开发中的重要语言,...

    flash 烟花效果源码,非常漂亮

    2. **上升阶段**:使用`Tween`类或` ENTER_FRAME`事件,随着时间的推移逐步改变烟花的位置,模拟上升过程。你可以调整上升速度,让烟花看起来更自然。 3. **绽放阶段**:到达最高点时,烟花开始绽放。这通常通过...

    Android_自定义切换控件SwitchView

    9. **测试与调试**:在完成自定义控件的编写后,需要在实际的Android设备或模拟器上进行测试,确保控件的功能正常,无明显的性能问题或视觉异常。 10. **集成到项目**:最后,将自定义的SwitchView类打包成库,或者...

    Unity3D 画面扭曲切换效果脚本

    - `TransitionType`: 定义了四种过渡类型:Left(左)、Right(右)、Up(上)、Down(下)。 3. **函数定义**: - `Awake()`: 在脚本实例创建后立即调用,用于确保只有一个`ScreenWipe`实例。 - `OnGUI()`: 用于...

    javaFx 部分代码

    定义了一个`Group`类型的变量`canvas`,用于存放所有绘制的元素。 ##### 5. 撤销功能实现 ```java function undo() { def index = sizeof(canvas.content); if (index &gt; 0) { delete canvas.content[index - 1]...

Global site tag (gtag.js) - Google Analytics