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

2019阿里云全部产品优惠券(好东东,强烈推荐)
领取地址 https://promotion.aliyun.com/ntms/yunparter/invite.html

推荐:HTML5 Canvas实现360度全景图

[HTML5 Canvas实现购物网站360度旋转物品实景图。]

昨晚有朋友问:
引用 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有了更新,

推荐:Android利用canvas画各种图形(点、直线、弧、圆、椭圆、文字、矩形、多边形、曲线、圆角矩形)

[1、首先说一下canvas类:Class OverviewThe Canvas class holds the draw calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canv

则对应的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也行。
顺便一提,那两个按钮我只是想测试一下数据源是否确实被更新了而已。实际上没啥用,可以忽略……

推荐:Android——Canvas类的使用

[转自:http://blog.sina.com.cn/s/blog_61ef49250100qw9x.html 主要是Canvas类(android.graphics.Canvas)。Canvas类就是表示一块画布,你可以在上面画你想画的东西。当然

相关推荐