今天很开心的收获: 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. 用WP_Query自定义WordPress 主循环

    我们知道操作 WordPress 主循环(WordPress Loop)最容易的方法是使用 query_posts 函数. 但是使用 query_posts 直接修改 WordPress 默认的主循环 ...

  2. ZOJ3741 状压DP Eternal Reality

    E - Eternal Reality Time Limit:2000MS     Memory Limit:65536KB     64bit IO Format:%lld & %llu S ...

  3. django动态表格总结

    应用场景: A与B之间存在一对多关系. CBV实现方案: CreateView/UpdateView + inlineformset + jquery 具体: view方面:重写post/get方法, ...

  4. localResizeIMG

    http://think2011.net/localResizeIMG/test/ 演示一下 自己试试 点我直接进入演示页面 说明 在客户端压缩好要上传的图片可以节省带宽更快的发送给后端,特别适合在移 ...

  5. lz4,pigz,gzip 3者比较

    一.压缩(1.1)使用gzip进行打包:# time tar -zcf tar1.tar binlog*real 0m48.497suser 0m38.371ssys 0m2.571s (1.2)使用 ...

  6. ICMP-type对应表

    一次在某个防火墙配置策略里看到如下的代码: iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT iptables -A FORWARD -p icmp ...

  7. linux awk 内置函数详细介绍(实例)

    这节详细介绍awk内置函数,主要分以下3种类似:算数函数.字符串函数.其它一般函数.时间函数 一.算术函数: 以下算术函数执行与 C 语言中名称相同的子例程相同的操作: 函数名 说明 atan2( y ...

  8. 【Linux】/dev/null 2>&1 详解

     今天一个朋友突然在自己的维护的Linux中, /var/spool/cron/root 中看到了以下的内容: 30 19 * * * /usr/bin/**dcon.sh > /dev/nul ...

  9. 编译安装0bda 8179无线网卡

    CentOS下安装USB无线网卡(Obda:8179) 参考:http://blog.163.com/thinki_cao/blog/static/83944875201311593529913/ c ...

  10. js ==与===区别(两个等号与三个等号)

    1.对于string,number等基础类型,==和===是有区别的 1)不同类型间比较,==之比较“转化成同一类型后的值”看“值”是否相等,===如果类型不同,其结果就是不等 2)同类型比较,直接进 ...