wpf mvc_使用BabySmash学习WPF-MVC或MVP以及设计者的好处

wpf mvc

wpf mvc

NOTE: If you haven't read the first post in this series, I would encourage you do to that first, or check out the BabySmash category. Also check out http://windowsclient.net/ for more developer info on WPF.

注意:如果您还没有阅读本系列第一篇文章,我建议您先阅读该文章,或者查看BabySmash类别 另请访问http://windowsclient.net/以获取有关WPF的更多开发人员信息。

BACKGROUND: This is one of a series of posts on learning WPF. I wrote an application for my 2 year old using WPF, but as I'm a Win32-minded programmer, my working app is full of Win32-isms. It's not a good example of a WPF application even though it uses the technology. I'm calling on community (that's you, Dear Reader) to blog about your solutions to different (horrible) selections of my code. You can get the code http://www.codeplex.com/babysmash. Post your solutions on your blog, in the comments, or in the Issue Tracker and we'll all learn WPF together. Pick a single line, a section, or subsystem or the whole app!

背景:这是有关学习WPF的一系列文章之一。 我使用WPF为2岁的孩子编写了一个应用程序,但是由于我是位Win32程序员,所以我的工作应用程序充满了Win32-ism。 即使它使用WPF技术,也不是WPF应用程序的好例子。 我正在呼吁社区(即您,亲爱的读者)在博客中介绍您针对我的代码的不同(糟糕)选择的解决方案。 您可以获取代码http://www.codeplex.com/babysmash 。 将您的解决方案发布到您的博客,评论或问题跟踪器中,我们将一起学习WPF。 选择一条线,一个部分,子系统或整个应用程序!

The sheer breadth and depth of WPF is really overwhelming some times. I find myself finding three or four different ways to accomplish basically the same thing and not knowing which one to choose. One wants to balance maintainability and a sense of code aesthetic (Code Smell) as well as focusing on extensibility. All this combined with my own "hacker's impatience" for new features has made messing with BabySmash! a very interesting exercise.

WPF的绝对广度和深度确实令人不知所措。 我发现自己找到了三种或四种不同的方法来完成基本相同的事情,却不知道该选择哪一种。 人们希望在可维护性和代码美感(Code Smell)之间取得平衡,同时还要关注可扩展性。 所有这些,加上我自己对新功能的“黑客的不耐烦”,使BabySmash陷入困境! 一个非常有趣的练习。

Last week I did a podcast with Felix Corke and Richard Griffin from Conchango on the Developer/Designer relationship and how they navigate their way through the development process and the tools that enable them or hamper their progress.

上周,我与来自Conchango的Felix CorkeRichard Griffin进行关于开发人员/设计人员关系的播客以及他们如何在开发过程中导航,以及实现或阻碍他们前进的工具

So far BabySmash has been just basic shapes drawn by me with some help from you, Dear Reader. And let me just say, few of you are designers from the looks of the XAML you've been sending me. ;)

到目前为止,亲爱的读者,BabySmash只是我在您的帮助下绘制的基本形状。 而且,我只是想说,从发送给我的XAML外观来看,你们当中很少有人是设计师。 ;)

Here's BabySmash before Felix got to the XAML and before I refactored it to support working with him.

这是Felix进入XAML之前以及我对其进行重构以支持与他合作之前的BabySmash。

Here it is now (and it's got animations and interactions, but you'll have to run it to see):

现在就到了(它有动画和交互,但是您必须运行它才能看到):

There's also a bunch of new features that have been made possible by refactoring the project to an MVP (Model-View-Presenter) pattern. But let's start at the beginning.

通过将项目重构为MVP(Model-View-Presenter)模式,还提供了许多新功能。 但是,让我们从头开始。

设计者的好处和关注点分开 (The Benefits of a Designer and Separated Concerns)

I opened my email yesterday and discovered a file sent by Flex called "allshapes1.xaml."

我昨天打开电子邮件,发现Flex发送的一个名为“ allshapes1.xaml”的文件。

ASIDE: I'm starting to realize that just like regular non-WPF .NET folks can't do everything in Visual Studio, this is especially true when coding in WPF. I'm regularly bouncing between these tools:

ASIDE:我开始意识到,就像普通的非WPF .NET人员无法在Visual Studio中做所有事情一样,在WPF中进行编码时尤其如此。 我经常在这些工具之间跳来跳去:

I busted out KaXAML and was greeted with this!

我淘汰了KaXAML,并对此表示欢迎!

After I finished crying and wallowing in self pity over my own lack of design skills, I set to figuring out how to exploit benefit from Felix's abilities. When I showed Felix and Richard my initial version of BabySmash last month in Olso, I was using Figures and Shapes and Geometries as my Units of Work. They both suggested I consider UserControls as a better way to work, especially when a Designer gets involved. I mostly ignored them, as I was getting along OK.

当我完成哭泣和自怜了我自身缺乏设计技能打滚,我设置为搞清楚如何 利用 从Felix的能力受益。 当我上个月在奥尔索向Felix和Richard展示我的BabySmash初始版本时,我将图形,形状和几何用作我的工作单位。 他们俩都建议我将UserControls视为更好的工作方式,尤其是在Designer参与其中时。 我大部分时间都忽略了它们,因为我过得很好。

However, when Felix's art showed up, it clicked for me. I took each of his shapes and made them UserControls, then modified my factory (FigureGenerator) to make UserControls instead.

但是,当费利克斯的艺术出现时,它吸引了我。 我将他的每个形状制作为UserControls,然后修改了我的工厂(FigureGenerator)以制作UserControls。

用户控件作为工作单元 (UserControls as Unit of Work)

The UserControls are self-contained, easy to make from Blend, and as soon as I made one Visual Studio prompted me to reload the project (as Blend and Visual Studio share the SAME csproj/vbproj/sln files) and there it was.

UserControl是自包含的,很容易用Blend制作,当我制作一个Visual Studio时,我就立即提示我重新加载项目(因为Blend和Visual Studio共享SAME csproj / vbproj / sln文件)就在那里了。

The UserControls compartmentalized everything (duh) nicely so I could start thinking about animations without messing up my business logic or cluttering up my (already cluttered) code.

UserControls很好地划分了所有内容(duh),因此我可以开始考虑动画而不会弄乱我的业务逻辑或弄乱我的代码(已经很混乱)。

添加动画 (Adding Animation)

Felix drew faces on the shapes, so I asked if he'd include animation. Minutes later he had markup in the XAML to make the eyes blink. Animation in WPF is declarative and time-based (not frame-based). He inserted some key frames and set the animation to repeat forever. Now, the shapes blink occasionally and I didn't have to write any code or worry about threading.

费利克斯(Felix)在形状上绘制了面Kong,所以我问他是否要加入动画。 几分钟后,他在XAML中进行了标记,使眼睛眨了眨眼。 WPF中的动画是声明性的且基于时间的(不是基于帧的)。 他插入了一些关键帧,并将动画设置为永远重复。 现在,形状偶尔会闪烁,而我不必编写任何代码或担心线程问题。

Even better, when I do another animations from code, his animations continue! This means, the shape's faces blink, the shapes fade away after a way, and if you click on them they'll jiggle. Three different animations done in different ways, all happening simultaneously.

更好的是,当我用代码制作另一个动画时,他的动画继续! 这意味着,形状的面Kong会眨眼,形状会在某种程度上消失,如果单击它们,它们会抖动。 三种以不同方式完成的动画同时发生。

Take the Square for example. You start with the basic shape. Notice is has an "x:Name." We can refer to anything with a name later, either in Code or XAML.

以广场为例。 您从基本形状开始。 通知中有一个“ x:Name”。 我们以后可以在Code或XAML中引用任何具有名称的东西。

<Rectangle x:Name="Body" StrokeThickness="10" Stroke="#ff000000" Width="207" Height="207">
</Rectangle>

Then, he gives it a nice radial fill. Note that he's doing all this work in Blend. I find the XAML building up interesting, myself.

然后,他给它一个很好的径向填充。 请注意,他正在Blend中完成所有这些工作。 我自己发现XAML建立起来很有趣。

<Rectangle x:Name="Body" StrokeThickness="10" Stroke="#ff000000" Width="207" Height="207">
<Rectangle.Fill>
<RadialGradientBrush MappingMode="Absolute" GradientOrigin="110.185547,455" Center="110.185547,455" RadiusX="98.5" RadiusY="98.5">
<RadialGradientBrush.Transform>
<MatrixTransform Matrix="1,0,-0,-1,-6.685547,558.5" />
</RadialGradientBrush.Transform>
<GradientStop Offset="0" Color="#ffff00ff"/>
<GradientStop Offset="1" Color="#ff9d005c"/>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>

Then he added a face. Note the the Face and Eyes are named.

然后他加了一张脸。 请注意,“面部和眼睛”已命名。

<Canvas x:Name="Face">
<Canvas x:Name="Eyes">
<Path Fill="#ff000000" Data="...snip..."/>
<Path Fill="#ff000000" Data="...snip..."/>
</Canvas>
<Path Fill="#ff000000" Data="...snip..."/>
</Canvas>

Then he makes the animation a Resource, and sets a Trigger that starts the animation when we're loaded:

然后,他将动画作为资源,并设置一个触发器,在加载时启动动画:

<UserControl.Resources>
<Storyboard x:Key="Eyes">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="Eyes"
Storyboard.TargetProperty="(UIElement.Opacity)"
RepeatBehavior="Forever">
<SplineDoubleKeyFrame KeyTime="00:00:02.1000000" Value="1"/>
<SplineDoubleKeyFrame KeyTime="00:00:02.1000000" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:02.300000" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:02.300000" Value="1"/>
<SplineDoubleKeyFrame KeyTime="00:00:10.300000" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<UserControl.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource Eyes}"/>
</EventTrigger>
</UserControl.Triggers>

This background animation all lives in the XAML and works without my code having to do anything.

该背景动画全部存在于XAML中,并且无需我的代码即可执行任何操作。

I wanted an interaction animation. I could have probably done it in XAML with a trigger, but in code it was just this. I took the animations from the WPF AnimationBehaviors project on CodePlex but did them with code rather than in XAML so they can apply to any UserControl including ones that I haven't added yet.

我想要一个互动动画。 我本来可以在XAML中使用触发器来完成此操作的,但在代码中仅此而已。 我从CodePlex上WPF AnimationBehaviors项目中获取了动画,但使用代码而不是XAML进行了动画处理,因此它们可以应用于任何UserControl,包括我尚未添加的那些。

void AllControls_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
UserControl f = sender as UserControl;
if (f != null && f.Opacity > 0.1) //can it be seen?
{
Animation.ApplyRandomAnimationEffect(f, Duration.Automatic);
PlayLaughter();
}
}

Still there are a half-dozen different ways to do things, so I'm still trying to find balance. I can see one going too far left or right and doing everything in XAML or Code even when it's uncomfortable. BabySmash is a hybrid until someone can help me understand better when to do one over the other.

仍然有六种不同的做事方式,所以我仍在努力寻找平衡。 我可以看到一个东西向左或向右走得太远,即使在XAML或Code中感到不舒服时,它也会执行所有操作。 BabySmash是一种混合动力,直到有人可以帮助我更好地了解何时一个一个做另一个。

MVP和MultiMonitor (MVP and MultiMonitor)

Both Ian Griffiths and Karl Shifflett said I needed to refactor things such that I wasn't putting all the logic in the MainWindow's code behind file. They said that WPF lends itself to an MVP (Model-View-Presenter) pattern, even if you're not really strict about it.

伊恩·格里菲斯( Ian Griffiths)卡尔·希夫莱特( Karl Shifflett)都说我需要重构某些东西,以免将MainWindow的所有逻辑都放在文件后面。 他们说WPF使自己适合于MVP(模型-视图-演示器)模式,即使您对此并不十分严格。

I realized I was going to need to do this soon as my first attempt at Multi-Monitor sucked so bad I ended up yanking it. Initial revisions of BabySmash! had a MainWindow class and all the logic in the code-behind, just like I would have in a WinForms application. The application start up and the Application class would spin through all the Monitors making a window for each one. This had a number of problems:

当我第一次尝试使用Multi-Monitor时,我意识到我将需要立即执行此操作,以至于我以失败告终。 BabySmash的初始版本! 就像我在WinForms应用程序中那样,在MainCode中有一个MainWindow类和所有逻辑。 应用程序启动,Application类将遍历所有Monitor,为每个Monitor创建一个窗口。 这有很多问题:

  • The shapes would only appear on the monitor whose Window had focus. You had to change focus with the mouse.

    这些形状只会出现在“窗口”具有焦点的监视器上。 您必须使用鼠标更改焦点。
  • You could close a Window or two on secondary monitors without closing all of them.

    您可以在辅助监视器上关闭一两个窗口而不关闭所有监视器。

Now, there's a single Controller class that manages as many Windows as it needs to. The app starts up like this:

现在,只有一个Controller类可以管理所需数量的Windows。 该应用程序如下启动:

private void Application_Startup(object sender, StartupEventArgs e)
{
Controller c = new Controller();
c.Launch();
}

And Controller.Launch looks like:

Controller.Launch看起来像:

public void Launch()
{
foreach (WinForms.Screen s in WinForms.Screen.AllScreens)
{
MainWindow m = new MainWindow(this)
{
WindowStartupLocation = WindowStartupLocation.Manual,
Left = s.WorkingArea.Left,
Top = s.WorkingArea.Top,
Width = s.WorkingArea.Width,
Height = s.WorkingArea.Height,
WindowStyle = WindowStyle.None,
Topmost = true
};
m.Show();
m.WindowState = WindowState.Maximized;
windows.Add(m);
}
}

Pretty simple to start. I should have smelt that something was wrong with the initial plan as I felt like I was "chasing my tail" in code, trying to get things to work in the original pattern. When I switched to this pattern things just became easy.

开始非常简单。 我应该闻到最初的计划有什么问题,因为我觉得自己在代码中“追尾”,试图使事情按原始模式运行。 当我切换到这种模式时,事情变得简单了。

Now, Why is this Model View Presenter and not Model View Controller (especially considering I called the class Controller)? Well, Phil does a good job answering this:

现在,为什么是这个Model View Presenter而不是Model View Controller(特别是考虑到我称之为类Controller)? 好吧, Phil在回答这个问题上做得很好

With MVC, it’s always the controller’s responsibility to handle mouse and keyboard events. With MVP, GUI components themselves initially handle the user’s input, but delegate to the interpretation of that input to the presenter. This has often been called “Twisting the Triad”, which refers to rotating the three elements of the MVC triangle and replacing the “C” with “P” in order to get MVP.

使用MVC,处理鼠标和键盘事件始终是控制器的责任。 使用MVP,GUI组件本身最初会处理用户的输入,但会委托给演示者解释该输入。 这通常被称为“扭曲三合会”,是指旋转MVC三角形的三个元素,并用“ P”代替“ C”以获得MVP。

Now I need to go learn more about Supervising Controller and Passive View as Martin Fowler suggested retiring the MVP pattern in favor of those two variants. The code is still in a sloppy stage (up on CodePlex) but I'd love to have someone (Phil?) who is familiar with pure instances of these patterns to help me tidy it up. I didn't take testing into consideration before (mistake) and I need to get back on the Righteous Path otherwise the technical debt is going to crush me. That's what I get for not going TDD from the start.

现在,我需要了解有关监督控制器被动视图的更多信息,因为马丁·福勒( Martin Fowler)建议停用MVP模式,以支持这两个变体。 代码仍处于草率阶段(在CodePlex上),但是我很乐意让一个熟悉这些模式的纯实例的人(Phil?)来帮助我进行整理。 我之前没有考虑过测试(错误),我需要回到正义之路,否则技术债务将压垮我。 这就是我从一开始就不进行TDD的收获。

The MainWindow's code-behind is just a bunch of small methods that delegate off to the Controller. If there are n MainWindows there's still just the single Controller. MainWindow is full of these kinds of things:

MainWindow的背后代码只是一堆委托给Controller的小方法。 如果有n个MainWindows,则仍然只有一个Controller。 MainWindow充满了这些东西:

protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
e.Handled = true;
controller.ProcessKey(this, e);
}

Every method is less than 20 lines, and most are really simple and boring, which is good.

每种方法都少于20行,而且大多数方法确实简单无聊,这很好。

private void AddFigure(FrameworkElement uie, string s)
{
FigureTemplate template = FigureGenerator.GenerateFigureTemplate(s);
foreach (MainWindow m in this.windows)
{
UserControl f = FigureGenerator.NewUserControlFrom(template);
m.AddUserControl(f);

f.Width = 300;
f.Height = 300;
Canvas.SetLeft(f, Utils.RandomBetweenTwoNumbers(0, Convert.ToInt32(m.ActualWidth - f.Width)));
Canvas.SetTop(f, Utils.RandomBetweenTwoNumbers(0, Convert.ToInt32(m.ActualHeight - f.Height)));

Storyboard storyboard = Animation.CreateDPAnimation(uie, f,
UIElement.OpacityProperty,
new Duration(TimeSpan.FromSeconds(Settings.Default.FadeAfter)));
if (Settings.Default.FadeAway) storyboard.Begin(uie);

IHasFace face = f as IHasFace;
if (face != null)
{
face.FaceVisible = Settings.Default.FacesOnShapes ? Visibility.Visible : Visibility.Hidden;
}
f.MouseLeftButtonDown += HandleMouseLeftButtonDown;
}
FiguresCount++;
PlaySound(template);
}

So far my biggest problems are moving things around, trying to decide "who is responsible for what." Given Animations, Sounds, Shapes, Faces, and all that, where and who is responsible for what, while keeping an eye open for extensibility.

到目前为止,我最大的问题是四处移动,试图确定“谁对什么负责”。 给定动画,声音,形状,面Kong等所有内容,在哪里以及谁负责什么,同时还要对扩展性保持警惕。

小巧之处-如果选中了复选框,则启用文本框 (The Little Niceties - Enabling a TextBox if a CheckBox IsChecked)

One little aside to end on. Just when I'm getting really pissed at WPF and I'm ready to give up, something simple and cool happens where I realize I'm starting to "get" it.

一旁结束。 就在我真的对WPF感到非常生气并且准备放弃时,一些简单而有趣的事情发生了,我意识到我已经开始“了解”它了。

For example. I've got this Options Dialog that you might remember Jason Kemp refactored. All the controls live inside a Grid and that Grid has a "DataContext" that is my Settings object. All the controls get bound to the object and I don't have to do any loading of values or pulling of values. It just works.

例如。 我有一个“选项对话框”,您可能还记得Jason Kemp重构了。 所有控件都位于Grid内,并且该Grid的“ DataContext”是我的Settings对象。 所有控件都绑定到该对象,并且我不必进行任何值的加载或值的提取。 它就是有效的。

I added that last checkbox and a new option where I wanted to Fade Shapes Away in x seconds. I wanted to disable the TextBox if the Checkbox was not checked. This is the kind of typical operation you might find yourself writing code for in WinForms. You'd hook up events to watch if it's Checked or not, then set the Enabled property of the TextBox, and you also have to watch for the initial load of state. It's not hard in WinForms, but it's irritating, tedious and it's in two places in the code behind.

我添加了最后一个复选框和一个新选项,我想在x秒内淡化Shapes Away。 如果未选中复选框,我想禁用TextBox。 您可能会发现自己是在WinForms中编写代码的典型操作。 您将挂接事件以查看是否已选中,然后设置TextBox的Enabled属性,还必须注意状态的初始加载。 在WinForms中这并不难,但是令人烦恼,乏味,并且在后面的代码中有两个地方。

Baby Smash! - Options (2)

Even though the DataContext (the thing we are data-binding to) is the Settings object, I can bind objects together by using the ElementName. Check this out. Look at the TextBox's IsEnabled property.

即使DataContext(我们要绑定数据的东西)是Settings对象,我也可以使用ElementName将对象绑定在一起。 看一下这个。 查看TextBox的IsEnabled属性。

<StackPanel Orientation="Horizontal" 
Grid.Row="6" Grid.ColumnSpan="2" HorizontalAlignment="Stretch">
<CheckBox x:Name="FadeChecked" Margin="15,0,0,0"
IsChecked="{Binding Path=Default.FadeAway,Mode=TwoWay}" >
Fade Shapes Away in</CheckBox>
<TextBox Margin="5,0,0,0"
Text="{Binding Path=Default.FadeAfter}"
IsEnabled="{Binding ElementName=FadeChecked,Path=IsChecked,Mode=TwoWay}"
Height="20" Width="25" />
<TextBlock>secs.</TextBlock>
</StackPanel>

It's a tiny victory, but it made me happy.

这是一次微不足道的胜利,但这使我感到高兴。

Related Links

相关链接

翻译自: https://www.hanselman.com/blog/learning-wpf-with-babysmash-mvc-or-mvp-and-the-benefits-of-a-designer

wpf mvc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值