今天很开心的收获: 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中发现了有人有比较类似的问题已经得到了解决

https://social.msdn.microsoft.com/Forums/vstudio/en-US/59a58867-352e-4c00-9ef2-5e2201ad18c6/bind-listbox-to-canvas-children?forum=wpf

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的方式的更多相关文章

  1. Android 在布局容器中动态添加控件

    这里,通过一个小demo,就可以掌握在布局容器中动态添加控件,以动态添加Button控件为例,添加其他控件同样道理. 1.addView 添加控件到布局容器 2.removeView 在布局容器中删掉 ...

  2. VC中动态添加控件

    VC中动态添加控件 动态控件是指在需要时由Create()创建的控件,这与预先在对话框中放置的控件是不同的. 一.创建动态控件: 为了对照,我们先来看一下静态控件的创建. 放置静态控件时必须先建立一个 ...

  3. 怎样在不对控件类型进行硬编码的情况下在 C#vs 中动态添加控件

    文章ID: 815780 最近更新: 2004-1-12 这篇文章中的信息适用于: Microsoft Visual C# .NET 2003 标准版 Microsoft Visual C# .NET ...

  4. MFC中动态添加控件----寻找多年的秘籍,吐血推荐

    原文作者tianwaik 动态控件是指在需要时由Create()创建的控件,这与预先在对话框中放置的控件是不同的. 一.创建动态控件 为了对照,我们先来看一下静态控件的创建. 放置静态控件时必须先建立 ...

  5. android 在布局中动态添加控件

    第一步 final LayoutInflater inflater = LayoutInflater.from(this); 第二步:获取需要被添加控件的布局 final LinearLayout l ...

  6. WinForm中动态添加控件 出现事件混乱,解决办法记录。

    还是在抢票软件中出的问题,我没点击一个联系人,要生成一排控件,其中有席别combobox这样的下拉框控件,会出现如下图所示的问题:问题描述:在代码中动态创建的控件,事件混乱了,一个控件触发了所有同类型 ...

  7. WPF:理解ContentControl——动态添加控件和查找控件

    WPF:理解ContentControl--动态添加控件和查找控件 我认为WPF的核心改变之一就是控件模型发生了重要的变化,大的方面说,现在窗口中的控件(大部分)都没有独立的Hwnd了.而且控件可以通 ...

  8. winform导入导出excel,后台动态添加控件

    思路: 导入: 1,初始化一个OpenFileDialog类 (OpenFileDialog fileDialog = new OpenFileDialog();) 2, 获取用户选择文件的后缀名(s ...

  9. jQuery EasyUI动态添加控件或者ajax加载页面后不能自动渲染问题的解决方法

    博客分类: jquery-easyui jQueryAjax框架HTML  现象: AJAX返回的html无法做到自动渲染为EasyUI的样式.比如:class="easyui-layout ...

随机推荐

  1. 小白科普之JavaScript的数组

    一.与其他语言数据的比较    相同点:有序列表    不同点:js的数组的每一项可以保存任何类型的数据:数组的大小是可以动态调整的 二.数组创建的两种方法 1)  var colors = new ...

  2. HDU 1029 Ignatius and the Princess IV

    解题报告: 题目大意:就是要求输入的N个数里面出现的次数最多的数是哪一个,水题.暴力可过,定义一个一位数组,先用memset函数初始化,然后每次输入一个数就将下标对应的上标对应的那个数加一,最后将整个 ...

  3. sql注入学习小结

    /* 转载请注明出处,By:珍惜少年时 小知识,只是放在博客吃饭时无聊看看,大牛勿喷. */ 珍惜少年时博客,专注网络安全 web渗透测试 00x1爆所有库: mysql> select sch ...

  4. python中import和from...import...的区别

    python中import和from...import...的区别: 只用import时,如import xx,引入的xx是模块名,而不是模块内具体的类.函数.变量等成员,使用该模块的成员时需写成xx ...

  5. 18个网站SEO建议

    第一位专家是Autotrader公司的搜索市场经理Dewi Nawasari,她认为SEO就是优化网站,以吸引你的目标客户的过程.她的建议如下: 1.创建良好的引导链接 要把用户的使用过程尽量的简化, ...

  6. qcow2文件压缩

    qemu-img convert -O qcow2 /path/old.img.qcow2 /path/new.img.qcow2 转自:https://havee.me/linux/2011-09/ ...

  7. 创建一个最简单的Linux随机启动服务

    转自: http://xiaoxia.org/2011/11/15/create-a-simple-linux-daemon/

  8. 《转》.NET开源核心运行时,且行且珍惜

    转载自infoQ 背景 InfoQ中文站此前报道过,2014年11月12日,ASP.NET之父.微软云计算与企业级产品工程部执行副总裁Scott Guthrie,在Connect全球开发者在线会议上宣 ...

  9. php中static静态关键字的使用

    php中除了常规类和方法的使用,访问控制之外,还有静态关键字static,静态变量可以是局部变量也可以是全局变量,当一个程序段执行完毕时,静态变量并没有消失,它依然存在于内存中,下次在定义时还是以前的 ...

  10. 【USACO】sprime

    有了前面的基础,做这道题真是so easy啊. 因为要分解后每个数都是素数,所以采用先生成短的素数,长的素数在短素数的基础上生成. 比如长度为1的素数只有 2 3 5 7, 那么符合要求的长度为2的素 ...