在WPF中 有如下两个控件 面板是wpf canvas 边框问题是 我想将左上角控件的绿端链接到右下角的红端,用折线链接

WPF中动态创建和删除控件 - 简书
WPF中动态创建和删除控件
WPF中动态创建和删除控件 15:30阅读:1,949动态创建控件1.容器控件.RegisterName('Name',要注册的控件) //注册控件2.容器控件.FindName('Name') as 控件类型 //找到控件并转换成相应类型注意:仅通过 控件.Name来设置是不能通过FindName来找到控件的,必须注册动态删除控件1.容器控件.Children.Remove(控件) //移除控件2.容器控件.UnregisterName('Name') //取消注册其它设置1.控件.SetValue(Grid.RowProperty, 0); //设置控件在Grid面板中的行数控件.SetValue(Grid.ColumnProperty, 1); //设置控件在Grid面板中的列数2.Canvas.SetLeft('控件名', 50); //设置控件在Canvas面板中的横坐标Canvas.SetTop('控件名', 50); //设置控件在Canvas面板中的纵坐标eg:当点击Button_Add按钮的时候在Canvas里添加一个Button;当点击Button_Remove的时候把这个新加的Button移除;设:Canvas 的Name为 canvas复制代码Xprivate void Button_Add_Click(object sender, Syste m.Windows.RoutedEventArgs e){Button btn = new Button();//btn.Name = 'newButton';//这里设置的Name是找不到的btn.Width = 100;btn.Height= 50;canvas.Children.Add(btn);canvas.RegisterName('newButton', btn);//注册名字,以便以后使用}private void Button_Remove_Click(object sender, RoutedEventArgs e){Button btn = canvas.FindName('newButton') as B//找到刚新添加的按钮if (btn != null)//判断是否找到,以免在未添加前就误点了{canvas.Children.Remove(btn);//移除对应按钮控件canvas.UnregisterName('newButton');//还需要把对用的名字注销掉,否则再次点击Button_Add会报错}}
我们不生产水,我们只是大自然的搬运工
大家一起努力学习!Posts - 610, Comments - 29791
最近在搞WPF开发,这对我来说是个陌生的领域。话说回来,可能是缺少耐心的缘故,我现在学习新事物的方式主要是“看一些入门文档”,“看一些示例”,然后“猜测”其实现并摸索着使用。在很多时候这种做法问题不大,但一旦有地方猜错了,但在一段时间里似乎和实践还挺吻合的,则一旦遇到问题就会卡死。上周五我就被一个WPF绑定的问题搞得焦头烂额,虽说基本搞定,但还是想验证下是否会有更好的做法,特此记录一下,欢迎大家指正。
目标与障碍
简单地说,我想做的事情是编写一个“绑定友好”的用户控件,它可以像Telerik的RadNumericUpDown控件那样使用:
&telerik:RadNumericUpDown Value=&{Binding Path=NumberValue}& /&
我们可以通过控件属性的形式直接绑定一个值上去,看上去应该是最基本的要求吧?那么我们就来实现一个类似的控件,他有两个属性,一个是Text字符串属性,另一个是Number整型属性,分别交由一个文本框和一个滑动条来控制。
&UserControl&
&StackPanel&
&TextBox Text=&...& /&
&Slider Minimum=&0& Maximum=&100& Value=&...& /&
&/StackPanel&
&/UserControl&
自然,MVVM是不可或缺的,因为在真实环境中一个用户控件的逻辑也会颇为复杂,我们需要对模型和界面进行分离。这个最简单的ViewModel定义如下(自然,实际情况下还需要实现INotifyPropertyChanged接口):
public class ValueInputViewModel : INotifyPropertyChanged
public string Text { get; set; }
public int Number { get; set; }
我之前都是使用DataContext作为ViewModel的容器,例如在BadValueInput.xaml.cs中:
public partial class BadValueInput : UserControl
public BadValueInput()
InitializeComponent();
this.DataContext = new ValueInputViewModel();
于是便可以在BadValueInput.xaml里绑定:
&TextBox Text=&{Binding Path=Text, UpdateSourceTrigger=PropertyChanged}& /&
&Slider Minimum=&0& Maximum=&100& Value=&{Binding Path=Number}& /&
然后再定义两个依赖属性即可。接着我们在MainWindow.xaml里使用这个类,同样使用MVVM模式:创建MainWindowViewModel类型,包含MyText和MyNumber两个属性,实例化并赋值给MainWindow的DataContext,然后在XAML里绑定至BadValueInput的两个属性上:
&view:BadValueInput Text=&{Binding Path=MyText}& Number=&{Binding Path=MyNumber}& /&
从我的设想中,这种做法没有任何问题:父控件(MainWindow)和子控件(BadValueInput)都有自身的DataContext,互不影响。父控件将自己的MyText和MyNumber分别绑定至子控件的Text和Number属性上,也符合直觉,但执行后的结果却并非如此:
System.Windows.Data Error: 40 : BindingExpression path error: 'MyText' property not found on 'object' ''ValueInputViewModel' (HashCode=6943688)'. BindingExpression:Path=MyT DataItem='ValueInputViewModel' (HashCode=6943688); target element is 'BadValueInput' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'MyNumber' property not found on 'object' ''ValueInputViewModel' (HashCode=6943688)'. BindingExpression:Path=MyN DataItem='ValueInputViewModel' (HashCode=6943688); target element is 'BadValueInput' (Name=''); target property is 'Number' (type 'Int32')
在Output窗口中出现了这样两条错误信息,意思是ValueInputViewModel上没有MyText和MyNumber两个属性。于是我就搞不懂了,为什么定义在MainWindow里的绑定使用的会是BadValueInput的DataContext,而不是当前上下文,即MainWindow的DataContext?我始终觉得这是种违反直觉的逻辑。
不使用DataContext作为ViewModel容器
我在微薄上提出这个问题之后收到了不少回应,很多朋友说是使用RelativeSource就可以解决问题,也就是让子控件可以找到父控件的DataContext,甚至说直接在子控件里直接指定父控件ViewModel路径。对于这个做法我不敢苟同,在我看来子控件应该是可以独立地自由使用的一个组件,它不应该根据父控件去调整自己的实现。
因此,即便这样的做法可以解决这一场景下的问题,但在我看来这完全属于在“凑”结果。我需要的是尽可能完善的解决方案,就像RadNumericUpDown那样干净清爽。我认为程序员还是需要一点完美主义,而不是仅仅为了解决问题而运用Workaround。
其实解决方案也很简单,告诉我,假如要避免出现这种情况,应该避免使用DataContext作为ViewModel容器,严格来说这是一种轻度滥用。其实只要遵循这个原则,这个问题也很容易解决。例如,在ValueInput.xaml.cs里定义个ViewModel属性:
public partial class ValueInput : UserControl
public ValueInput()
: this(new ValueInputViewModel())
public ValueInput(ValueInputViewModel viewModel)
InitializeComponent();
this.ViewModel = viewM
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register(&ViewModel&, typeof(ValueInputViewModel), typeof(ValueInput));
public ValueInputViewModel ViewModel
get { return (ValueInputViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
然后在ValueInput.xaml里绑定时指定特定的成员:
&UserControl x:Class=&WpfUserControl.Views.ValueInput&
x:Name=&Self&&
&StackPanel&
&TextBox Text=&{Binding ViewModel.Text, ElementName=Self, UpdateSourceTrigger=PropertyChanged}& /&
&Slider Minimum=&0& Maximum=&100& Value=&{Binding ViewModel.Number, ElementName=Self}& /&
&/StackPanel&
&/UserControl&
如今的绑定不光指定Path,还会使用ElementName将Source定义成当前控件对象。不过接下来的问题是,如何将控件的Text和Number属性,与ViewModel中的属性关联起来呢?目前我只知道使用代码来实现这种同步,这需要我们在ValueInput.xaml里添加更多代码:
public ValueInput(ValueInputViewModel viewModel)
InitializeComponent();
viewModel.PropertyChanged += (_, args) =&
if (args.PropertyName == &Text&)
if (!String.Equals(viewModel.Text, this.Text))
this.Text = viewModel.T
else if (args.PropertyName == &Number&)
if (!viewModel.Number.Equals(this.Number))
this.Number = viewModel.N
this.ViewModel = viewM
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
typeof(string),
typeof(ValueInput),
new FrameworkPropertyMetadata(OnTextPropertyChanged) { BindsTwoWayByDefault = true });
private static void OnTextPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs args)
var valueInput = (ValueInput)o;
if (!String.Equals(valueInput.ViewModel.Text, valueInput.Text))
valueInput.ViewModel.Text = valueInput.T
public string Text
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
public static readonly DependencyProperty NumberProperty =
DependencyProperty.Register(
typeof(int),
typeof(ValueInput),
new FrameworkPropertyMetadata(OnNumberPropertyChanged) { BindsTwoWayByDefault = true });
private static void OnNumberPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs args)
var valueInput = (ValueInput)o;
if (!valueInput.ViewModel.Number.Equals(valueInput.Number))
valueInput.ViewModel.Number = valueInput.N
public int Number
get { return (int)GetValue(NumberProperty); }
set { SetValue(NumberProperty, value); }
为了将控件上属性的改变同步至ViewModel,我们在定义依赖属性的时候提供propertyChangedCallback参数。同理,为了将ViewModel上属性的改变同步至控件,我们会监听ViewModel对象的PropertyChanged事件,这样的做法虽然麻烦,但的确管用。
如果我们要在MainWindow里使用这个控件,我们可以继续使用DataContext,此时子控件的DataContext属性会获取到父控件的DataContext对象,这自然不会出现取不到属性的问题。当然,如果父控件本身也希望成为一个独立控件的话,也可以使用同样的做法,即创建自身的ViewModel属性并使用ElementName指定Source,继续避免对DataContext产生依赖。
示例代码与疑问
。我周五遇到的这个问题算是这么解决了,但其实我还是有一些疑问,例如:有没有更简单的做法?ValueInput.xaml.cs里用于同步控件属性与ViewModel属性的代码实在太多,也很容易写错。此外,能否在控件上定义一个只读的属性?例如代码中我额外添加的ReadOnlyValue依赖属性:
public static readonly DependencyProperty ReadOnlyValueProperty =
DependencyProperty.Register(&ReadOnlyValue&, typeof(int), typeof(ValueInput));
public int ReadOnlyValue
get { return (int)GetValue(ReadOnlyValueProperty); }
private set { SetValue(ReadOnlyValueProperty, value); }
但是一旦缺少公开的setter,在XAML里就无法绑定这个属性了,即便我把绑定的Mode设为OneWayToSource。初学WPF,疑问很多,希望大家多多帮助。
Categories:
Control的使用者,通常是View,才会有对应的ViewModel。(View和Control之间还是有区别的)
顺便再提两点:
可重用的Control通常实现成 CustomControl,方便Templating和Styling。UserControl就非常受限制。
实际的项目中 this.DataContext = new XxxViewModel() 的形式并不多见,通常只会在顶层View出现,而内层View/Control的DataContext往往从顶层继承,或者继承自顶层DataContext的某个属性。也会有项目采用IoC的方式对DataContext注入,不过这些都跟Control本身的实现无关了。
我没有下载您的代码查看,我自己的理解是:对于一个UserControl,如何正确的处理其ViewModel,DependencyProperties,以及子控件属性这三者的同步关系。
我的建议如下:
不要Hardcode自己的DataContext(否则使用控件的人会很难受)
首先DP与ViewModel上的属性进行双向绑定(by code)
第二子控件的属性与对应的DP进行双向绑定(by xaml)
这样的代码应该是比较简单的,希望这个信息对您有用。
一个 vm 可以给多个 view 共享 (list / chart)
可以换一套 skin, 或者 control lib,或者干脆整个 view... w/o changing vm
所以 vm doesn't know view...
但是 control 本身再实现 vm,除了第一条符合,后面貌似都不太符合... 会不会有些 over design??
如果 mvvm 可以,mvc 是不是也可以?反正这个 view logic 就是给这个 view 用的,vm can know and can access view... which makes mvc also make sense here, right...
遇到一个属性(XML attribute)的时候(包括嵌套的属性,例如Grid.Rows),给对应的属性赋值,或者设置绑定(其实是MarkupExtension,绑定是其中的一种)
对于元素嵌套元素,则是将子元素作为值赋给父元素的Content Property(由父元素类上定义的ContentPropertyAttribute决定),这可以看作是嵌套属性的一种特殊情况。
所以,对于任何一个元素,XAML Parser都是先构造一个默认对象,然后设置它的各属性值/绑定。基于以上的逻辑,如果控件在默认构造函数中对自己的公开属性进行了任何赋值,那么就面临着被用户覆盖的风险。例如:
控件构造函数中对自己的Number属性SetBinding,是会被使用者XAML中的Number="10"或Number="{Binding MyNumber}"覆盖的。这样构造函数设置的绑定就完全没用了。
控件构造函数中设置了自己的DataContext,例如this.DataContext = new MyViewModel(),也是会被使用者XAML中的 DataContext="{StaticResource AnotherViewModel}" 甚至是 DataContext="{x:Null}" 覆盖。其实这些就是最简单的.NET属性赋值,绑定可以认为是一种特殊的值。
反过来的一个特殊情况,任何FrameworkElement的DataContext在没有明确设置值的时候,它的值不是null,也不是任意值,而是从父节点递归继承,可以认为它的初始值是一个绑定:DataContext="{Binding Parent.DataContext, RelativeSource=Self}"。一旦在构造函数中设置了 this.DataContext = new MyViewModel(),那么这个值就覆盖了初始值,自然也就不能从父节点继承了。
至于控件需不需要ViewModel,我个人的经验是,如果硬要套MVVM模式,控件这个类本身就是最好的ViewModel,它定义的所有公开DP是可供使用的ViewModel属性,而控件内部树的组成就是它的View, View上使用TextBox、Slider等“基本控件”的DP绑定到控件上的DP。而当这个复杂控件被使用时,它本身又成为了View的一部分,它的DP又被绑定的业务逻辑的ViewModel上。
我现在能想到的一个例子,是WPF中Expander的实现(使用了ToggleButton、ContentPresenter组成?不是很确定了)。有兴趣的话可以Reflector看一下。
退一步讲,如果的确想在控件实现里使用ViewModel,那么这个ViewModel应该是控件的PrivateViewModel,逻辑关系应该是:TextBox/Slider的DP 绑定到 PrivateViewModel的属性Text/Number,PrivateViewModel的属性Text/Number绑定到控件本身的Text/Number DP。(注意,不能反过来,即控件本身的Text/Number DP绑定到PrivateViewModel,因为一旦这样做,使用者如果覆盖了绑定,控件就不能正常工作;使用者如果不覆盖绑定,那还用你的控件做什么?)这样一来,PrivateViewModel就必须要有“绑定”的功能,在WPF中,有绑定功能的对象就是DependencyObject了,那么与其再创建一个新的DependencyObject,纯粹为了满足MVVM pattern,倒不如直接使用控件本身这个DependencyObject。
已自动隐藏某些不合适的评论内容(),如需阅读,请准备好眼药水并后查看(如登陆后仍无法浏览请留言告知)。
,登陆后便可删除或修改已发表的评论
(请注意保留评论内容)WPF入门教程系列6——布局介绍与Canvas(一) - WPF当前位置:& &&&WPF入门教程系列6——布局介绍与Canvas(一)WPF入门教程系列6——布局介绍与Canvas(一)&&网友分享于:&&浏览:0次WPF入门教程系列六——布局介绍与Canvas(一)&&&&&& 从这篇文章开始是对WPF中的界面如何布局做一个较简单的介绍,大家都知道:UI是做好一个软件很重要的因素,如果没有一个漂亮的UI,功能做的再好也无法吸引很多用户使用,而且没有漂亮的界面,那么普通用户会感觉这个软件没有多少使用价值。
一.&总体介绍
& & & & WPF的布局控件都在System.Windows.Controls.Panel这个基类下面,使用&WPF提供的各种控件在WPF应用程序中界面进行布局,同时对各种子控件(如按钮、文本框,下拉框等)进行排列组合。
Pane类的公共属性太多了。就简单介绍几个常见的属性如下表。
获取或设置在鼠标指针位于此元素上时显示的光标。&
DataContext
获取或设置元素参与数据绑定时的数据上下文。&
Dispatcher
获取与此&关联的&Dispatcher。&
FontFamily
获取或设置控件的字体系列。&
获取或设置字号。&
FontWeight
获取或设置指定的字体的权重或粗细。&
Foreground
获取或设置描述前景色的画笔。&
HandlesScrolling
获取一个值控件是否支持滚动。
获取或设置元素的建议高度。&
HorizontalContentAlignment
获取或设置控件内容的水平对齐。&
获取一个值,该值指示是否已加载此元素以供呈现。&
IsMouseOver
获取一个值,该值指示鼠标指针是否位于此元素(包括可视树上的子元素)上。这是一个依赖项属性。&
获取或设置一个值控制是否在选项卡上导航包含。&
获取一个值,该值指示此元素在用户界面&中是否可见。这是一个依赖项属性。&
LayoutTransform
获取或设置在执行布局时应该应用于此元素的图形转换方式。&
获取或设置元素的外边距。&
获取或设置元素的标识名称。&该名称提供一个引用,以便当&处理器在处理过程中构造标记元素之后,代码隐藏(如事件处理程序代码)可以对该元素进行引用。
获取或设置当&在用户界面&(UI)&中呈现时为其整体应用的不透明度因子。这是一个依赖项属性。&
获取或设置控件中的空白。&
RenderTransform
获取或设置影响此元素的呈现位置的转换信息。这是一个依赖项属性。&
获取或设置使用&键时,确定顺序接收焦点的元素的值,当用户将控件定位。&
获取或设置任意对象值,该值可用于存储关于此元素的自定义信息。&
获取或设置在用户界面&中为此元素显示的工具提示对象。&
TouchesCaptured
获取在此元素上捕获的所有触摸设备。&
TouchesCapturedWithin
获取在此元素或其可视化树中的任何子元素上捕获的所有触摸设备。&
VerticalContentAlignment
获取或设置控件内容的垂直对齐方式。&
Visibility
获取或设置此元素的用户界面&可见性。这是一个依赖项属性。&
VisualOpacityMask
获取或设置&值,该值表示&Visual&的不透明蒙板。&
获取或设置元素的宽度。&
& & && 一个Panel&的呈现就是测量和排列子控件,然后在屏幕上绘制它们。所以在布局的过程中会经过一系列的计算,那么子控件越多,执行的计算次数就越多,则性能就会变差。如果不需要进行复杂的布局,则尽量少用复杂布局控件(如&Grid和自定义复杂的Panel);如果能简单布局实现就尽量使用构造相对简单的布局(如&Canvas、UniformGrid等),这种布局可带来更好的性能。&如果有可能,我们应尽量避免调用&UpdateLayout方法。&& & & 每当Panel内的子控件改变其位置时,布局系统就可能触发一个新的处理过程。对此,了解哪些事件会调用布局系统就很重要,因为不必要的调用可能导致应用程序性能变差。&& & && 换句话说,布局是一个递归系统,实现在屏幕上对控件进行大小调整、定位和绘制,然后进行呈现。具体如下图,要实现控件0的布局,那么先要实现0的子控件&01,02...的布局,要实现01的布局,那么得实现01的子控件001,002...的布局,如此循环直到子控件的布局完成后,再完成父控件的布局,&最后递归回去直到递归结束,这样整个布局过程就完成了.
&&&&& 布局系统为Panel中的每个子控件完成两个处理过程:测量处理过程(Measure)和排列处理过程(Arrange)。每个子&Panel&均提供自己的&MeasureOverride&和&ArrangeOverride&方法,以实现自己特定的布局行为。
二.&Canvas&
&&&&& Canvas是最基本的面板,只是一个存储控件的容器,它不会自动调整内部元素的排列及大小,它仅支持用显式坐标定位控件,它也允许指定相对任何角的坐标,而不仅仅是左上角。可以使用Left、Top、Right、&Bottom附加属性在Canvas中定位控件。通过设置Left和Right属性的值表示元素最靠近的那条边,应该与Canvas左边缘或右边缘保持一个固定的距离,设置Top和Bottom的值也是类似的意思。实质上,你在选择每个控件停靠的角时,附加属性的值是作为外边距使用的。如果一个控件没有使&用任何附加属性,它会被放在Canvas的左上方(等同于设置Left和Top为0)。
&&&&& Canvas的主要用途是用来画图。Canvas默认不会自动裁减超过自身范围的内容,即溢出的内容会显示在Canvas外面,这是因为默认&ClipToBounds=&False&;我们可以通过设置ClipToBounds=&True&来裁剪多出的内容。
接下来我们来看两个实例,第一个实例使用XAML代码实现:
&Window x:Class="WpfApp1.WindowCanvas"
xmlns="/winfx/2006/xaml/presentation"
xmlns:x="/winfx/2006/xaml"
Title="WindowCanvas" Height="400" Width="500"&
&Canvas Margin="0,0,0,0" Background="White"&
&Rectangle Fill="Blue"
Stroke="Azure"
Width="250"
Height="200"
Canvas.Left="210" Canvas.Top="101"/&
&Ellipse Fill="Red"
Stroke="Green"
Width="250" Height="100"
Panel.ZIndex="1"
Canvas.Left="65" Canvas.Top="45"/&
&Button Name="btnByCode" Click="btnByCode_Click"&后台代码实现&/Button&
实例后的效果如下图。
第二个实例,我们使用后台代码来实现。我使用C#来实现
using System.Collections.G
using System.L
using System.T
using System.Threading.T
using System.W
using System.Windows.C
using System.Windows.D
using System.Windows.D
using System.Windows.I
using System.Windows.M
using System.Windows.Media.I
using System.Windows.S
namespace WpfApp1
/// &summary&
/// WindowCanvas.xaml 的交互逻辑
/// &/summary&
public partial class WindowCanvas : Window
public WindowCanvas()
InitializeComponent();
public void DisplayCanvas()
Canvas canv = new Canvas();
//把canv添加为窗体的子控件
this.Content =
canv.Margin = new Thickness(0, 0, 0, 0);
canv.Background = new SolidColorBrush(Colors.White);
//Rectangle
Rectangle r = new Rectangle();
r.Fill = new SolidColorBrush(Colors.Red);
r.Stroke = new SolidColorBrush(Colors.Red);
r.Width = 200;
r.Height = 140;
r.SetValue(Canvas.LeftProperty, (double)200);
r.SetValue(Canvas.TopProperty, (double)120);
canv.Children.Add(r);
Ellipse el = new Ellipse();
el.Fill = new SolidColorBrush(Colors.Blue);
el.Stroke = new SolidColorBrush(Colors.Blue);
el.Width = 240;
el.Height = 80;
el.SetValue(Canvas.ZIndexProperty, 1);
el.SetValue(Canvas.LeftProperty, (double)100);
el.SetValue(Canvas.TopProperty, (double)80);
canv.Children.Add(el);
private void btnByCode_Click(object sender, RoutedEventArgs e)
DisplayCanvas();
&实现后的效果如下图。
最后&要说明一点Canvas内的子控件不能使用两个以上的Canvas附加属性,如果同时设置Canvas.Left和Canvas.Right属性,那么后者将会被忽略。
12345678910
12345678910
12345678910 上一篇:下一篇:文章评论相关解决方案 1234567891011 Copyright & &&版权所有}

我要回帖

更多关于 wpf canvas 缩放 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信