今天很开心的收获: 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. 使用 Python 创建你自己的 Shell(下)

    导读 在上篇中,我们已经创建了一个 shell 主循环.切分了命令输入,以及通过 fork 和 exec 执行命令.在这部分,我们将会解决剩下的问题.首先,cd test_dir2 命令无法修改我们的 ...

  2. C#内部类

    http://blog.csdn.net/yanghua_kobe/article/details/6685222 在<Java编程思想>中花了一章的篇幅介绍,内部类的相关特性.而在C#的 ...

  3. [BZOJ3670][UOJ#5][NOI2014]动物园

    [BZOJ3670][UOJ#5][NOI2014]动物园 试题描述 近日,园长发现动物园中好吃懒做的动物越来越多了.例如企鹅,只会卖萌向游客要吃的.为了整治动物园的不良风气,让动物们凭自己的真才实学 ...

  4. linux下proc里关于磁盘性能的参数

    我 们在磁盘写操作持续繁忙的服务器上曾经碰到一个特殊的性能问题.每隔 30 秒,服务器就会遇到磁盘写活动高峰,导致请求处理延迟非常大(超过3秒).后来上网查了一下资料,通过调整内核参数,将写活动的高峰 ...

  5. 【Spring】Spring系列1之Spring概述

    概述

  6. jquery check box

    if ($("#eulaLine").is(':checked')) { var mobile = $("#mobile").val(); if (mobile ...

  7. Java异常的栈轨迹fillInStackTrace和printStackTrace的用法

    本文转自wawlian 捕获到异常时,往往需要进行一些处理.比较简单直接的方式就是打印异常栈轨迹Stack Trace.说起栈轨迹,可能很多人和我一样,第一反应就是printStackTrace()方 ...

  8. tcp ip detatils

    tcp ip detatils 8.关于TCP协议,下面哪种说法是错误的()A.TCP关闭连接过程中,两端的socket都会经过TIME_WAIT状态B.对一个Established状态的TCP连接, ...

  9. coco2dx加载网络图片并保存

    直接上代码 bool HelloWorld::init() { ////////////////////////////// // 1. super init first if ( !Layer::i ...

  10. (转)SQL Server 中WITH (NOLOCK)浅析

    概念介绍 开发人员喜欢在SQL脚本中使用WITH(NOLOCK), WITH(NOLOCK)其实是表提示(table_hint)中的一种.它等同于 READUNCOMMITTED . 具体的功能作用如 ...