结合ItemsControl在Canvas中动态添加控件的最MVVM的方式
今天很开心的收获: ItemsControl 中 ItemsPanel的重定义和 ItemContainerStyle 以及 ItemTemplate 三者的巧妙结合,在后台代码不实例化任何控件的前提下,实现标准的MVVM模式下,在前台Canvas中动态创建包含各种数据展示形态的控件。
好东西要共享,先上简化过的XAML最终解决方案:
<UserControl.Resources>
<Style x:Key="MyItemsControlStyle" TargetType="ItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Left}" />
<Setter Property="Canvas.Top" Value="{Binding Top}" />
</Style>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate DataType="vm:MyItemViewModel">
<Border Width="120" Height="30" Background="Red">
<TextBlock Text="{Binding Name}" />
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources> <Grid>
<ItemsControl ItemsSource="{Binding ItemList}" Style="{StaticResource MyItemsControlStyle}" />
</Grid>
看到这里大家可能不是很明白其中的有趣之处,那么下面是解决问题的整个过程。
说需求:
1. 需要根据业务数据,在界面的自定义位置显示数据对象。
2. 希望采用更符合MVVM设计模式的方式,界面和业务分离,在业务层添加数据的同时,界面自动创建数据对象对应的控件。
分析:这里面的自定义位置,需要绝对定位,那么自然要用到Canvas。
很久以前的做法是: 1. 创建一个自定义控件A
2. 为自定义控件A扩展一堆自定义的属性。
3. 每次新增业务对象时,在后台代码New一个自定义控件A的实例。
4. Add到Canvas中,再按照业务数据,设置控件A的Canvas.Left和Canvas.Top。
这样的弊端是:如果业务数据频繁交互,那么Code-Behind中需要不停的引用界面中的控件,并使用代码维护和更新控件的各种属性。
以后一旦业务逻辑发生变更,后台代码中所有引用控件的地方都要跟着改动,类似过渡耦合导致的开发成本将会非常之高,最后变得不可维护。当然也有各种分层的方式可以很大程度上保持较高的扩展性和可维护性。但随着业务变化愈加复杂,随之而来的应对成本还是比较大的。想一想,还是有些不寒而栗。
我当然会继续使用界面和业务数据分离的方式来开发这个东西,但直到以我昨天对WPF的认知,想来想去也没有想明白该如何设置两个定位的值。
我起初尝试这样:
<UserControl.Resources>
<Style x:Key="MyItemsControlStyle" TargetType="ItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate DataType="vm:MyItemViewModel">
<Border Canvas.Left="{Binding Left}" Canvas.Top="{Binding Top}" Width="120" Height="30" Background="Red">
<TextBlock Text="{Binding Name}" />
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
必然不行,随后搜到了一位园友的文章。 http://www.cnblogs.com/fdyang/p/3877309.html
是个不错的方案,但有一点让我非常不舒服。就是在每个业务对象的数据模板中外面都包裹了一个Canvas,虽然这个Canvas是不可见的,不影响实际显示效果,但是如果我有一千个业务对象,界面就会创建一千个Canvas,而且所有的业务对象都不在同一个画布中,这无论如何不能忍···
随后在MSDN中发现了有人有比较类似的问题已经得到了解决
MSDN里面的解决方案如下:
<ListBox x:Name="testListBox" Width="300" Height="150">
<ListBox.Template>
<ControlTemplate TargetType="{x:Type ListBox}">
<Canvas Background="Gray" x:Name="CanvasPanel" IsItemsHost="True" />
</ControlTemplate>
</ListBox.Template >
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding (Canvas.Left)}"/>
<Setter Property="Canvas.Top" Value="{Binding (Canvas.Top)}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.Items>
<Rectangle Width="50" Height="25" Canvas.Left="10" Canvas.Top="50" Fill="BlueViolet"/>
<Ellipse Width="50" Height="75" Canvas.Left="75" Canvas.Top="20" Fill="Blue"/>
</ListBox.Items>
</ListBox>
恍然大悟:哦,怎么没有想到呢。用ItemContainerStyle 进行Canvas附加属性的绑定就可以了啊。我以前都是使用ItemContainerStyle 绑定依赖属性,竟然忘记也可以绑定附加属性了。那么我和他的差别就是,他绑定的是控件自身的附加属性,而我的附加属性的值来源于ItemViewModel。最后使用 DataTemplete 设置 ItemTemplete 的数据可视化模板就可以了。
于是问题就这样解决了。为了确认这样是靠谱的,我用XamlPad查看了下 Visual Tree。
逻辑树如下:
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<Border Width="20" Canvas.Left="40" Canvas.Top="20" Height="30" Background="Red"></Border>
<Border Width="20" Canvas.Left="80" Canvas.Top="40" Height="30" Background="Aqua"></Border>
</ItemsControl>
</Grid>
</Page>
可视树截图:

好,那么现在我在ViewModel中,只需要创建一个 MyItemViewModel 的集合,叫做ItemList, 并绑定到 ItemsControl 的 ItemsSource 上,由于 DataTemplete 的 Type 是 MyItemViewModel,我只需要在后台代码中向集合添加 MyItemViewModel类型的实例,界面就创建了对应的控件,一共4行代码的方法。
private void CreateMyItem()
{
ItemList.Add(new MyItemViewModel
{
Left = _rightButtonUpPoint.X,
Top = _rightButtonUpPoint.Y,
Name = string.Format("Left:{0} Top:{1}", _rightButtonUpPoint.X, _rightButtonUpPoint.Y)
});
}
最后上 Demo截图

本文原创,转载请注明出处。
结合ItemsControl在Canvas中动态添加控件的最MVVM的方式的更多相关文章
- Android 在布局容器中动态添加控件
这里,通过一个小demo,就可以掌握在布局容器中动态添加控件,以动态添加Button控件为例,添加其他控件同样道理. 1.addView 添加控件到布局容器 2.removeView 在布局容器中删掉 ...
- VC中动态添加控件
VC中动态添加控件 动态控件是指在需要时由Create()创建的控件,这与预先在对话框中放置的控件是不同的. 一.创建动态控件: 为了对照,我们先来看一下静态控件的创建. 放置静态控件时必须先建立一个 ...
- 怎样在不对控件类型进行硬编码的情况下在 C#vs 中动态添加控件
文章ID: 815780 最近更新: 2004-1-12 这篇文章中的信息适用于: Microsoft Visual C# .NET 2003 标准版 Microsoft Visual C# .NET ...
- MFC中动态添加控件----寻找多年的秘籍,吐血推荐
原文作者tianwaik 动态控件是指在需要时由Create()创建的控件,这与预先在对话框中放置的控件是不同的. 一.创建动态控件 为了对照,我们先来看一下静态控件的创建. 放置静态控件时必须先建立 ...
- android 在布局中动态添加控件
第一步 final LayoutInflater inflater = LayoutInflater.from(this); 第二步:获取需要被添加控件的布局 final LinearLayout l ...
- WinForm中动态添加控件 出现事件混乱,解决办法记录。
还是在抢票软件中出的问题,我没点击一个联系人,要生成一排控件,其中有席别combobox这样的下拉框控件,会出现如下图所示的问题:问题描述:在代码中动态创建的控件,事件混乱了,一个控件触发了所有同类型 ...
- WPF:理解ContentControl——动态添加控件和查找控件
WPF:理解ContentControl--动态添加控件和查找控件 我认为WPF的核心改变之一就是控件模型发生了重要的变化,大的方面说,现在窗口中的控件(大部分)都没有独立的Hwnd了.而且控件可以通 ...
- winform导入导出excel,后台动态添加控件
思路: 导入: 1,初始化一个OpenFileDialog类 (OpenFileDialog fileDialog = new OpenFileDialog();) 2, 获取用户选择文件的后缀名(s ...
- jQuery EasyUI动态添加控件或者ajax加载页面后不能自动渲染问题的解决方法
博客分类: jquery-easyui jQueryAjax框架HTML 现象: AJAX返回的html无法做到自动渲染为EasyUI的样式.比如:class="easyui-layout ...
随机推荐
- 忘记mysql root用户密码
今天帮一个售后的同事解决网盘无登录的问题,看了下后台日志,报错用密码root连接不上数据库,然后我就强行改了一下数据库密码,就OK了. (1)用root登录系统. (2)vim /etc/my.cnf ...
- back(返回)键总结
对于一般的android手机,都提供了四个(目前主流三个)按键.这几个按键中,back(返回)键用得比较多,对back键出发的事件一般是通过重写onKeyDown(int keyCode, KeyEv ...
- Android 字体和颜色
对于能够显示文字的控件(如TextView EditText RadioButton Button CheckBox Chronometer等等),你有时需要控制字体的大小.Android平台 ...
- Unity 利用NGUI做屏幕分辨率适配+学习UIDraggablePanel的使用
原地址:http://blog.sina.com.cn/s/blog_697b1b8c0101g2r4.html 大家使用unity,一定有看中其跨平台的强大功能,因此也难免会遇到不同屏幕分辨率适配的 ...
- Linux 怎么查看服务的启动进程所占用的目录
lsof简介 lsof(list open files)是一个列出当前系统打开文件的工具.在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件.所以 ...
- 再论pyquery
发现对于QQ群 空间文件的抓取毫无办法. QQ空间的代码可圈可点: 做了一个js的“客户端”,第一次加载时,将文件的列表信息全部抓取出来,然后基于js进行翻页和排序. 因此,想要抓取js渲染的dom, ...
- FFT(1)
FFT Complex struct complex{ double re,im; complex(double r,double i){re=r,im=i;} complex(){re=0.0,im ...
- 使用XmlWriter写Xml
假定创建了XmlWriter的实例变量xmlWriter,下文中将使用此实例变量写Xml 1.如何使用XmlWriter写Xml文档声明 ? // WriteStartDocument方法可以接受一个 ...
- 对于sharepoint 的解决方案的实际说明
对于sharepoint 的解决方案 实际上就是cab的包 你把***.wsp改为***.cab我们就可以查看这个包中的所有内容了
- 桐桐的贸易--WA
问题 A: 桐桐的贸易 时间限制: 1 Sec 内存限制: 64 MB提交: 15 解决: 2[提交][状态][讨论版] 题目描述 桐桐家在Allianceance城,好友ROBIN家在Horde ...