在WPF中减少逻辑与UI元素的耦合

原文:在WPF中减少逻辑与UI元素的耦合

            在WPF中减少逻辑与UI元素的耦合

周银辉

1,    避免在逻辑中引用界面元素,别把后台数据强加给UI

 一个糟糕的案例

比如说主界面上有一个显示当前任务状态的标签label_TaskState,我们会时常更新该标签以便及时地将任务状态通知用户。那么很糟糕的一种假设是我们的代码中会到处充斥着这样的语句段this.label_TaskState .Content = this.GetStateDescription(TaskStates.Busy);(GetStateDescription方法会返回一段比较友好的描述信息)

当用户点击“暂停”按钮后,我们可能要这样来这样更新标签:

void btn_Pause_Clicked(object sender, RoutedEventArgs e)

{

//do something to pause the task

//update our lab

this.label_TaskState .Content = this.GetStateDescription(TaskStates.Pause);

}

当由于某种原因我们的任务发生了错误时,我们可能会这样:

try

{

//do something dangerous

}

catch(MyException e)

{

this.label_TaskState .Content = this.GetStateDescription(TaskStates.Error);

}

finally

{

//…

}

这样一来,我们的逻辑代码无数地方将引用label_TaskState这个UI元素。

现在有一些变化来了:(1)我们觉得使用一段文本来描述任务状态还是不够直观,所以我们决定使用美工提供的一系列漂亮图标来显示当前状态(图标中也可能含有文字,不过我们不关心)。(2)另外一个面板上(myPanel2)也要放置一个显示任务当前任务状态的标签label_TaskState2,只不过其仅仅显示文字描述就可以了。

那么我们在这么糟糕的环境下是不是应该像这样来修改我们的代码呢?

首先找出所有引用了label_TaskState的地方(比如有20个)。

然后将Lable类型的label_TaskState控件修改为Image类型的image_TaskState控件。

然后重复地将this.label_TaskState .Content = this.GetStateDescription(TaskStates.Busy);语句替换为this.image_TaskState.Source = this.GetStateImage(TaskStates.Busy);

别忘了每次都要在该语句后追加一条:this.label_TaskState2.Content = this.GetStateDescription(TaskStates.Busy);因为我们增加了一个标签。

多么令人上火的编程工作啊。

原因是,我们频繁地引用不稳定的界面元素(label_TaskState),严重地将界面和逻辑耦合在了一起,我们采用赋值的方式将后台数据(当前状态信息)强加给了UI元素。

解决方案:使用Binding,然UI元素从后台“拿”数据

一个简单的描述是:后台逻辑对前台UI说“要如何展现由前台决定,数据就在这里,要用就自己来拿吧”

“数据就在这里”

我们的数据是当前任务的状态信息,为了提供给UI元素和后台逻辑使用,我们决定提供一个TheTaskState属性来跟踪当前状态:

public TaskStates TheTaskState

{

get

{

return (TaskStates)GetValue(TheTaskStateProperty);

}

set

{

SetValue(TheTaskStateProperty, value);

}

}

public static readonly DependencyProperty TheTaskStateProperty =

DependencyProperty.Register("TheTaskState", typeof(TaskStates),

typeof(Window1), new UIPropertyMetadata(TaskStates.Idle));

这样后台逻辑中要改变任务状态时只需要修改TheTaskState属性就可以了。

“要用就自己来拿吧”

当前台需要向用户展现该任务状态时只需要读取该属性,要实时跟踪就绑定吧。

<Label x:Name="label_TaskState"

Content="{Binding ElementName=windowMain,Path=TheTaskState}" />

“要如何展现由前台决定”

不对,我要展现给用户的可不是一些枚举值,而应该是图片或文本。

的确如此,所以我们要在绑定中加入转换器(或者数据模板,这里我们使用转换器):

public class TaskStatesImageConverter:IValueConverter

{

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

{

TaskStates state = (TaskStates)value;

return GetImageFromTaskState(state);

}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

{

throw new NotImplementedException();

}

private Image GetImageFromTaskState(TaskStates state)

{

Image image = new Image();

image.Source = new BitmapImage(new Uri((int)state+".png", UriKind.Relative));

return image;

}

}

<Label x:Name="label_TaskState"

Content="{Binding ElementName=windowMain,

Path=TheTaskState,

Converter={StaticResource myTaskStatesImageConverter}}" />

这样一来,我们的后台逻辑没有去引用UI元素并把数据强加给它,后台关注于如何任务状态及其更新,前台专注于如何向用户展现这些信息。当我们要更换其他展示方式时,只需更换一下转换器就可以了。

2,避免逻辑代码依赖Template中的元素

Template的目的是可更换,如果和逻辑耦合在一起就很有可能在更换Template的时候出现异常,也就是不可更换,模板就失去了意义。

糟糕的案例1

比如让我们来打造ScrollBar这样的控件,如果按照如下的方式来处理用户点击上ScrollBar两端的箭头就会出现问题:在ScrollBar的ControlTemplate的视觉树的两端分别是放置一个ToggleButton,以便用户点击这两个按钮可以上下(或左右)翻页,如何处理用户的点击事件呢?错误的方式是注册按钮的点击事件:

private void RepeatButton1_Click(object sender, RoutedEventArgs e)

{

RepeatButton rb = (RepeatButton)sender;

// left(or up) scrolling

}

private void RepeatButton2_Click(object sender, RoutedEventArgs e)

{

RepeatButton rb = (RepeatButton)sender;

// right(or down) scrolling

}

糟糕的案例2

有时会犯这样的错误:本来我们遵守着很多规范地使用ControlTemplate(DataTemplate是一样的道理)来将逻辑和UI很好的分开了(比如我们打造了一个不错的CustromControl),但突然发现似乎要在逻辑代码中引用ControlTemplate视觉树中的某个元素,然后发现FrameworkTemplate.FindName()可以完成这项工作,便出现了下面这样的代码:

<Style TargetType="{x:Type Button}">

<Setter Property="Template">

<Setter.Value>

<ControlTemplate TargetType="{x:Type Button}">

<Grid Margin="5" Name="grid">

<!--someting else-->

</Grid>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

Grid  gridInTemplate = (Grid)myButton1.Template.FindName("grid", myButton1);

//do something about the grid

这些都可以完成工作,但我们知道在WPF中,用户(你的控件用户,也可能是你自己)是可以定制ControlTemplate的,那么用户完全可以将逻辑引用到的这两个RepeatButton删掉或更换成其它元素,那么控件必定残缺甚至异常。如果不允许用户更改则失去了Template的意义。

解决方案:

经验是,当你觉得必须对视觉树中的元素进行事件注册以便挂接到某个事件处理方法上时,你可以想办法将方法所实现的功能包装成Command。比如案例1中,可以将滚动条的上下(或左右)翻页包装成形如ScrollBar.LineUpCommand、ScrollBar.LineDownCommand的形式,然后只需将视觉树中的表示上下翻页的元素的Command属性指定成他们就可以了。若仅仅是元素的某个事件将改变某些元素(或自己)的状态时,你可以使用Trigger来达到这一目的(也许你需要增加一些Dependency Property来充当Trigger的条件),比如:

<ControlTemplate.Triggers>

<Trigger Property="IsEnabled" Value="false">

<Setter Property="Background" TargetName="Bg" Value="Red "/>

</Trigger>

</ControlTemplate.Triggers>

绝对没有借口让逻辑部分引用DataTemplate的元素,可能会有逻辑引用ContorlTemplate中的元素的情况(打造某些CustomControl时),这时你可以使用TemplatePartAttribute来进行标识。

打造CustomControl时遇到的耦合问题,可以参考这篇文章:在WPF中自定义控件(3) CustomControl (下)

3,总结

总的说来,我们应该为逻辑和UI的解耦而努力,WPF也为我们提供了这样的机制。上面的例子仅仅是说明了常见的几种情况,而提供的解决方案也仅供参考,没有放之四海而皆准的方案,因为这其中涉及到了太多适应于不同情况下的小技巧。但总体而言:数据绑定、Style,Template,Command,Resource等为逻辑和UI的解耦提供了几条途径,如果你发现你的逻辑代码和UI元素严重地耦合在了一起而带来了不少麻烦,那么可以从上面的几条途径入手。另外,写这篇文字的最主要目的还是引起大家在实际编码过程中对逻辑和UI的解耦的重视。

原文地址:https://www.cnblogs.com/lonelyxmas/p/9047176.html

时间: 2024-10-05 18:32:47

在WPF中减少逻辑与UI元素的耦合的相关文章

[WPF自定义控件]?Window(窗体)的UI元素及行为

原文:[WPF自定义控件]?Window(窗体)的UI元素及行为 1. 前言 本来打算写一篇<自定义Window>的文章,但写着写着发觉内容太多,所以还是把使用WindowChrome自定义Window需要用到的部分基础知识独立出来,于是就形成了这篇文章. 无论是桌面编程还是日常使用,Window(窗体)都是最常接触的UI元素之一,既然Window这么重要那么多了解一些也没有坏处. 2.标准Window 这篇文章主要讨论标准的Window,不包括奇形怪状的无边框.非矩形Window,即只讨论W

拒绝卡顿——在WPF中使用多线程更新UI

有经验的程序员们都知道:不能在UI线程上进行耗时操作,那样会造成界面卡顿,如下就是一个简单的示例: ????public partial class MainWindow : Window????{????????public MainWindow()????????{????????????InitializeComponent();????????????this.Dispatcher.Invoke(new Action(()=> { }));????????????this.Loaded

CSharpGL(6)在OpenGL中绘制UI元素

CSharpGL(6)在OpenGL中绘制UI元素 主要内容 学习使用IUILayout接口及其机制,以实现在OpenGL中绘制UI元素. 以SimpleUIAxis为例演示如何使用IUILayout. 下载 您可以在(https://github.com/bitzhuwei/CSharpGL)找到最新的源码.欢迎感兴趣的同学fork之. 什么是OpenGL中的UI元素 您可以在源码中找到SimpleUIAxis这一示例. 如上图所示,有5个坐标轴,中间那个是一个普通的三维模型(元素),作为对照

在WPF中自定义控件

一, 不一定需要自定义控件在使用WPF以前,动辄使用自定义控件几乎成了惯性思维,比如需要一个带图片的按钮,但在WPF中此类任务却不需要如此大费周章,因为控件可以嵌套使用以及可以为控件外观打造一套新的样式就可以了.是否需要我们来自定义控件,这需要你考虑目前已有控件的真正逻辑功能而不要局限于外观,如果目前的控件都不能直觉地表达你的想法,那么你可以自己来打造一个控件,否则,也许我们仅仅改变一下目前控件的模板等就可以完成任务.很多人在自定义控件上经常犯的错误是:重复撰写已有的逻辑 二,UserContr

Silverlight及WPF中实现自定义BusyIndicator

在开发Silverlight或者WPF项目时,当我们调用Web服务来加载一些数据时,由于数据量比较大需要较长的时间,需要用户等待,为了给用户友好的提示和避免用户在加载数据过程中进行重复操作,我们通常使用BusyIndicator这个控件来锁定当前页面.然而,有时候BusyIndicator这个控件的风格和我们的界面风格并不搭配,而且修改起来也比较麻烦,今天我们就来自己写一个BusyIndicator控件,实现自定义的忙碌提示. 后面会提供源码下载.  一.实现基本原理及最终效果 我们先来看下面这

WPF中异步更新UI元素

XAML 界面很简单,只有一个按钮和一个lable元素,要实现点击button时,lable的内容从0开始自动递增. <Grid> <Label Name="lable_plus" Content="0"/> <Button Content="Button" Click="button_Click" Height="23" Name="button" Wid

如何监视 WPF 中的所有窗口,在所有窗口中订阅事件或者附加 UI

原文:如何监视 WPF 中的所有窗口,在所有窗口中订阅事件或者附加 UI 由于 WPF 路由事件(主要是隧道和冒泡)的存在,我们很容易能够通过只监听窗口中的某些事件使得整个窗口中所有控件发生的事件都被监听到.然而,如果我们希望监听的是整个应用程序中所有的事件呢?路由事件的路由可并不会跨越窗口边界呀? 本文将介绍我编写的应用程序窗口监视器,来监听整个应用程序中所有窗口中的路由事件.这样的方法可以用来无时无刻监视 WPF 程序的各种状态. 其实问题依旧摆在那里,因为我们依然无法让路由事件跨越窗口边界

基础篇-在非UI线程中更新UI元素

个人原创,转载请注明出处: http://blog.csdn.net/supluo/article/details/ 先了解两个概念 1.UI:User Interface的缩写,用户界面的意思.你可以不恰当的理解为我们能够看到的,操作的东西:在Android中什么才称为UI呢,可以简单的理解为View及其子类等元素.这是一个不够正确的概念,只是对新手做一个简单的抛砖引玉. 2.ANR:Application Not Responding,意思是程序没有响应. 在如下情况下,Android会报出

JQuery UI中的Tabs与base元素摩擦的BUG

JQuery UI中的Tabs与base元素冲突的BUG 以前一直使用jquery-ui-1.8,最近打算试一下目前最新的版本1.11.但对于Tabs,页面是乱的,怎么也不正常.折腾了好几个小时,最后发现页面中使用的base元素,对Tabs有破坏性的影响. 没有想清楚具体的原因,先记下来再说吧. 到了晚上,又想起这个问题.这个问题实在讨厌,我的系统中所有页面中都使用了base元素,不解决这个冲突实在是不爽.经过几个小时的跟踪调试,终于发现的问题所在: 新版本的jquery UI中,Tabs的代码