Home » WPFRSS

MVVM and Data Binding in a large project

We're looking to port a very large WF application (100+ screens in extensible modules) to WPF. 

We use Business Object extensively in the original application. These are designed around the business requirements and abstract the data persistence. We were excited about WPF being built around the idea of real tier separation. So far, most of us have yearned to go back to WF.

The main problem is the reliance on XAML (Declarative coding) and the difficulty / amount of code required to do some relatively simple things. For example, checking for a NOT-NULL value requires custom components and markup. 

One big issue has been that the display hierarchy doesn't always reflect the BO hierarchy. The standard data binding model uses simple reflection to locate binding sources, but sometimes logic is required to get / filter the displayed data. We've taken many approaches, including foregoing templates and building the UI with code, which obviously diminishes the value of using WPF. We've create a way to use Delegates to allow us to code complex bindings using code-behind, but this breaks the expression designer. The workaround involves adding another 4 lines of XAML. We just can win with this thing...

Also, there seems to be this idea in WPF that "everything should be done in XAML when possible." I assume this is a requirement that is borrowed from silverlight, because it doesn't work well at all for large WPF windows / controls. We try to break out user controls whenever possible, but still end up with 1,000+ line XAML files that are cumbersome and reminiscent of the massive VB6 .bas files of yore. Not only is a large C# file MUCH more manageable than a large XAML file, but XAML tends to be much more verbose than it's C# counterparts.

Any advice would be appreciated. We want this to work: we want to provide our clients with a rich interface. AJAX and HTML5 in a hosted control seems like a more appealing approach day by day... :( 
 

12 Answers Found

 

Answer 1

In the WPF world binding is king, unlike Windows Forms world (for most of us back then).  The MVVM pattern/model takes almost every single concern of yours and neatly tucks it into a highly workable pattern.  So for the XAML layer, all you are really interested in is Control layout, User events and proper binding.  This means that if you need a special filter you put it in the Model or ViewModel, and not necessarily the XAML layer. 

The XAML or View only transmits its needs to the ViewModel and Model via a Command object.  The ViewModel is the DataContainer of the View.  So if you want to filter something based on User input, then a command is sent to be processed.  It usually will notify the Model of what needs to be done, the model abstracts everything away from the view and back ends the data.  When the Model has completed it's work, it can post an event to the ViewModel to say "update that collection", upon update, the View is notified of new data and refreshes accordingly.  No code behind in like in the WinForm days.  Rather a well orchestrated series of events and responsibilities performed only by those responsible.

 

View--->ViewModel--->Model  (Commands float around between View, ViewModel and Model, depending on implementation).  Best of all, every single one of these classes is "Loosely coupled", which means you can easily maintain each layer as changes are needed. 

Before you get too far, read up and understand MVVM, then you'll know why you're doing this and the best way to go...

 

 

Answer 2

XAML files with 1000 lines of code?  Sounds a bit high to me.  Take something as simple as a Shell layout.  You will typically have a Title area, Navigation Panel, Main Panel and status area.  Now there's only 4 content controls for the shell and less than 20 lines of XAML.

From there you learn how to inject UserControls by setting the Shell's ViewModel contentcontrol place holder with the content.  If you repeat this same abstract pattern for all controls you wind up with a bunch of fundamental abstract-like UserControls each of which can be injected at will.  A title for example is one line of code <TextBlock> inside of the UserControl.  You can alter content easily by just altering the ViewModel String Property for that Textblock.  So now you have a universal title, and a univeral look and feel for entire application because it is all plugged into the Shell.  This means you can with one statement change the theme of your application at anytime by keying on the Shell....Very powerfull stuff.

 

Answer 3

 I don't know what type of advice you are looking for.  In my experience, people coming from WinForms struggle more with WPF, than say a web developer.  Things are done completely different in WPF as compared to win forms.  I work on very large engineering applications that would blow your mind in terms of size, and have never really experience the problems you are describing.  I have never had the need to create the UI from code, except for an occasional dynamic DataTemplate.  I have never had issues with complex hierarchy. Then again, I don't use the supplied controls from Microsoft in those types of applications.  I understand that some views can get quite large and the XAML can seem never ending.  This is where the help of Blend comes in.  Although, I myself am not a huge Blend fan and prefer to hand type my views.  But it doesn't matter what your views are written in.  A large view will contain tons of some type of code, if it is Xaml or C# or HTML.

I am not saying WPF is a silver bullet, and it does have it's flaws.  Most of the problems people run into are releated to not knowing how to do it in WPF and they try a Win Form approach, which just complicates things.  Either way, you have to choose what technolofy to use to build your apps based on requirements, environment, and the skills of your team.

By the way, if you think HTML5 and AJAX is a more appealing approach, then have fun with that :0)

EDIT: If you do decide to move to WPF I highly recommend using Prism: http://compositewpf.codeplex.com/

 

Answer 4

Thanks this is exactly the type of feedback I was soliciting.

I don't like MVVM because (1) it's more code to debug and (2) it's redundant and (3) it dictates a performance hit conceptually and (4) we were able to achieve the same results in WF without the need for a view model. I was hoping there would be a more effective alternative.

For now, we've created a {CodeBinding} markup extension that functions like a {Binding} except it binds to a function rather than a reflected property. This allows us to insert logic into the binding without needing a View Model. This more closely matches what we were able to do before in WF without needing all the additional code dictated by a view model. Unfortunately this breaks the Blend designer...

I'll look into the different MVVM frameworks, but where MVVM has really failed for us is when dealing with nested and hierarchical data. The view-model becomes very complex and unwieldy. This is also where our XAML becomes similarly complex and unwieldy, where triggers, templates, and styles litter the top 80% of the file.

A lot of these ideas look really appealing on paper and work very well for small, simple applications, but in practice and for more complex apps, it simply hasn't lived up to the hype. I'll post up some examples, so if we're doing something wrong, maybe someone can help point out the errors...

 

Answer 5

The diffculty you are describing defintely has some "design smell" to it, which could be the source of your frustrations.  There really shouldn't be any redundancy.  I am curious on how you are implementing your view models.  Hopefully you are not deriving from DependencyObject or FrameworkElement.  We will help out if we can.
 

Answer 6

Robert,

I do not agree with you. Here are my arguments:

1) No, if you use an external library, it is actually less code to debug (and if you write unit tests, which is one of the goals of MVVM, you have to debug even less)

2) In what way do you think it's redundant? I don't see it.

3) Nice animations are also a performance hit, but computers nowadays allow you to do this. I don't think it hits so much performance you will notice it.

4) No, you were not. You couldn't separate your UI logic (which is the view model) from your model, and test those separately. With WinForms, you always needed a manual tester, or automate the UI to run automated tests. With MVVM, you no longer have to do this.

I don't recommend to create your custom markup extension. It's easier to bind to an object, and then use a converter to return the result. Even better, just return the result by a property, then the updates are reflected via the INotifyPropertyChanged events.

Also, about the nested and hierarchical data, I do not agree. When I started developing Catel, I couldn't find any good solutions to created nested user controls and keeping the VM simple. Therefore, we have implemented a solution in Catel. You can find more information here.

The only recommendation I can give you is: don't give up on MVVM, I really think you are not seeing the power of it (yet). Also, try using Catel, it is really easy to use, and you will notice that MVVM really adds value to larger projects (especially when you start writing unit tests for your view models).

 

Answer 7

Thanks for all of your feedback.

What we first tried was not MVVM per se: we simply wrapped our BO's in classes that represented the data specifically for the UI. It's essentially the same thing, but minus a few of the standards MVVM defines.

What I like about MVVM is the idea that I can have a simple object that represents the state of the screen, minus all of the UI-specific functions and members, and generic members these controls provide. This is a great way to test UI workflow with unit testing, however I'm not sure the cost in implementation is worth the savings in QA. 

Here's the situation we ran into when we tried implementing a ViewModel:

A library we have handles network data packets. It's a library of interfaces that looks something like this:

IProtocol
 - Parse(IPacket)
 - Slice(IPacket)

IPacket
 - TimeReceived DateTime
 - Stream Stream
 - Messages IEnumerable<IMessage> 
 - Slices IEnumerable<IMessageSlice>

IMessage
 - Name String
 - Type MessageType
 - Protocol IProtocol

IMessageSlice
 - Message IMessage 
 - Name String
 - Value String
 - IEnumerable<IMessageSlice>

The protocol analyzer displays a list of packets, and a decoder displays the message slices (Human-readable decoded "slices" of the messages in the packet).

The decoder itself is a user control that displays message slices, grouped by message in expanders. Each expander contains a tree view displaying each message's slices (since a Message slice may also contain message slices and is a infinitely deep hierarchy).

For example, a packet would look like this in the UI:

^ UDP - Datagram            <-- IMessage (Expander)
 > Source: 192.0.0.0        <-- IMessageSlice (TreeItem)
 > Destination: 192.0.0.0  <-- IMessageSlice (TreeItem)
^ Protocol1 - Request
 > Terminal Data
    > Terminal Name: Foo
    > Software Version: foo 1.0
 > Update Request
    > Software Version: foo 2.0

 

The trick here is that when a packet is sliced, its slices are placed in the Packet object (Packet.Slices) and NOT the respective message (Message.Slices): this has to be resolved by the UI logic (Message.Slices = Packet.Slices.Select(x=>x.Message = Message)).

We tried to create a kind of ViewModel and bind to that instead (Pseudo-code below, of course....)

class PacketSurrogate
{
  List<MessageSurrogate> Messages {get;set;}
  PacketSurrogate(IPacket packet} {...}
}

This wrapped the packet and contained the logic required for the UI. It required 3 new classes and a fair amount of coding to tie it all together. This is what I'm referring to when I talk about redundancy: we created a nearly identical objects to what we already had for a single binding nuance. Ultimately we scrapped this approach because of the effort and went with adding a CodeBinding to the messages tree view, which was simple and effective. We did in 4 lines of code what took a couple hundred in surrogate classes (a ViewModel).

Geert, what I described was a separation of Business Logic and UI logic in Windows Forms,. The UI logic was contained in the component and the Business logic in the BO library. This allowed us to unit test the BO library. I'm not sold on the idea of further dividing the UI into two layers (UI Logic and Presentation logic?). The only benefit I can see from that is automated UI workflow testing. Since a human (QA) must inspect the UI functionality before it's released anyway, we've always rejected UI automation and UI workflow testing as redundant. The cost in supporting the automated tests exceeded the cost of QA (QA is cheaper than engineering). I'm absolutely open to new ideas, here though, especially if it enables us to use WPF more effectively.

If you perhaps use my above example to propose a solution, that would help me out. The problem as I see it is the amount of effort required (Cost $$$$$) to get a WPF solution up, and if Catel is the solution, that appears to be fixing the problem of "too much code" by adding more code (and another learning curve) for our developers, if I can be frank :)

 

Thanks!

 

Answer 8

Concerning the parsing of packets, wouldn't a class like this work?

^ UDP - Datagram            <-- IMessage (Expander)
 > Source: 192.0.0.0        <-- IMessageSlice (TreeItem)
 > Destination: 192.0.0.0  <-- IMessageSlice (TreeItem)
^ Protocol1 - Request
 > Terminal Data
    > Terminal Name: Foo
    > Software Version: foo 1.0
 > Update Request
    > Software Version: foo 2.0

In your class you define a Datagram Type, Source and Destination Address strings, Protocol1, TerminalWrapper...

Your parsing then is separated from plugging the conent, after parsing you simply new up a UDP class instance

UPD newPacket = new UPD();

Source = Parsed.Source;

Destination = Parsed.Destination.

Then your ViewModel is of type UDP Packet and you can set up a DataGrid with an item template...  So your entire collection's binding is handled by the ItemTemplate.  (Kind of like repeater controls in ASPX)...

 

 

Answer 9

BTW it took me about a year to 1.5 years of WPF work to really get comfortable with it and be dangerous...
 

Answer 10

When you are talking about the separation of UI and its logic, I think you misunderstand the Model in MVVM. The Model is the business object (or database entity, or whatever). It's a data object that contains information, and can be shared over several projects (for example, we use an entity library for your main database that can be used over several projects). Then, you want to do something with the data in a specific UI element. You can either do this in your code behind (but this requires manual testing), or in your model. Implement the logic in the model is against my rules to follow "separation of concerns". Who says the model should behave the same in another view? It's a data object containing data, not implementing functionality.

Furthermore, I totally agree that a QA team should always manually check UI. However, what if the project comes to an end. I have been in some large projects, and the first thing they cut off is the QA team and some developers. If you had created unit tests that test the UI-specific behavior (in MVVM, the view model), you can more safely add new functionality without breaking existing code. Of course the developer itself always takes a look at the code, but developers make (lots of) mistakes (otherwise, QA wouldn't even exist). Writing unit tests at first might seem dull and time-consuming, but the pay-back comes when the projects comes to an end, or people are getting removed (because of budget, or whatever).

When I started WPF (and appr 1,5 years later, MVVM), I was sceptic too. But now, I wouldn't go back to WinForms very easily (actually, I refused a few jobs simple because they were using WinForms and weren't interested in WPF).

Now, back to your situation. The fact that you had to create 3 different classes isn't a problem to me at all. It's called separation of concerns (data (M), UI behavior (VM) and actual UI (V)). 

In your case, you say you needed to create 3 additional classes. I think it's just one, namely the ViewModel. I understand that you need to write a lot of code to make this functionality yourself. Luckily, there are tons of frameworks that help you create easier classes. For example, in your case (this is of course a very simple example), your view model in Catel would look like the code below. Note that I used Catel because it can map properties from/to models with attributes, which makes it much easier to create the view model you are looking for. Don't worry about the lines of code, all is created in just a few minutes using code snippets. The problem is that there is no actual functionality in your UI (for what I can see). Anyway, below are 2 view models. The first one is a packet view model. It needs an IPacket instance to be created. Then, there is also a MessageSliceViewModel which contains the view data for a message slice (which in your case is just the slice itself + its children).

///<summary>/// Packet view model.///</summary>publicclass PacketViewModel : ViewModelBase
{
  #region Constructor & destructor
  ///<summary>/// Initializes a new instance of the <see cref="PacketViewModel"/> class.///</summary>///<param name="packet">The packet.</param>public PacketViewModel(IPacket packet)
    : base()
  {
    Packet = packet;
  }
  #endregion#region Properties
  ///<summary>/// Gets the title of the view model.///</summary>///<value>The title.</value>publicoverridestring Title { get { return"View model title"; } }

  #region Models
  ///<summary>/// Gets or sets the packet.///</summary>
  [Model]
  public IPacket Packet
  {
    get { return GetValue<IPacket>(PacketProperty); }
    privateset { SetValue(PacketProperty, value); }
  }

  ///<summary>/// Register the Packet property so it is known in the class.///</summary>publicstaticreadonly PropertyData PacketProperty = RegisterProperty("Packet", typeof(IPacket));
  #endregion#region View model
  ///<summary>/// Gets or sets the messages.///</summary>
  [ViewModelToModel("Packet")]
  public IEnumerable<IMessage> Messages
  {
    get { return GetValue<IEnumerable<IMessage>>(MessagesProperty); }
    set { SetValue(MessagesProperty, value); }
  }

  ///<summary>/// Register the Messages property so it is known in the class.///</summary>publicstaticreadonly PropertyData MessagesProperty = RegisterProperty("Messages", typeof(IEnumerable<IMessage>));

  ///<summary>/// Gets or sets the slices.///</summary>
  [ViewModelToModel("Packet")]
  public IEnumerable<IMessageSlice> Slices
  {
    get { return GetValue<IEnumerable<IMessageSlice>>(SlicesProperty); }
    set { SetValue(SlicesProperty, value); }
  }

  ///<summary>/// Register the Slices property so it is known in the class.///</summary>publicstaticreadonly PropertyData SlicesProperty = RegisterProperty("Slices", typeof(IEnumerable<IMessageSlice>));
  #endregion#endregion
}

///<summary>/// Slice view model.///</summary>publicclass SliceViewModel : ViewModelBase
{
  #region Constructor & destructor
  ///<summary>/// Initializes a new instance of the <see cref="SliceViewModel"/> class.///</summary>public SliceViewModel(IMessageSlice messageSlice)
    : base()
  {
    MessageSlice = messageSlice;
  }
  #endregion#region Properties
  ///<summary>/// Gets the title of the view model.///</summary>///<value>The title.</value>publicoverridestring Title { get { return"View model title"; } }

  #region Models
  ///<summary>/// Gets or sets the message slice.///</summary>
  [Model]
  public IMessageSlice MessageSlice
  {
    get { return GetValue<IMessageSlice>(MessageSliceProperty); }
    privateset { SetValue(MessageSliceProperty, value); }
  }

  ///<summary>/// Register the MessageSlice property so it is known in the class.///</summary>publicstaticreadonly PropertyData MessageSliceProperty = RegisterProperty("MessageSlice", typeof(IMessageSlice));
  #endregion#region View model
  ///<summary>/// Gets or sets the message slices.///</summary>
  [ViewModelToModel("MessageSlice")]
  public IEnumerable<IMessageSlice> MessageSlices
  {
    get { return GetValue<IEnumerable<IMessageSlice>>(MessageSlicesProperty); }
    set { SetValue(MessageSlicesProperty, value); }
  }

  ///<summary>/// Register the MessageSlices property so it is known in the class.///</summary>publicstaticreadonly PropertyData MessageSlicesProperty = RegisterProperty("MessageSlices", typeof(IEnumerable<IMessageSlice>));
  #endregion#endregion#endregion
}

 

Answer 11

@ Mr Javaman:

The classes actually do indeed look like that, however in this particular scenario we're using just the interfaces which provide a common contract for the functionality that is required across all packet types. For example, some packets don't go over IP, so source / destination addresses aren't part of the interfaces. Also, the reason the slices are stored in the IPacket and not the IMessage is performance: transmission performance is preferred over debug performance, so some compromises were made in the design of the interfaces. We have to absorb these in the UI logic.

 

@Geert:

Thanks for taking the time to provide me with a robust example. I love unit testing and we use it extensively as part of our BO library. I would like to use it for the UI as well if we can control the cost. Ultimately, if we can just get the WPF development cost  <=  the WF development cost, that alone would be a win.

Now that we have screens already done, I should be able run through a few to get a feel of how it might impact our development. I don't mind taking a hit to promote a consistent process over a series of hacks, which is what we have now. Thanks for helping me get started. I'll post back here when I have some results (probably won't be for a few days, at least).

 

 

Thanks!

 

Answer 12

Great, looks like this thread is coming up with a good solution for you...of course as you know only Testing proves anything.  Test Away!!!!  I'm a huge fan of test and my day job is being Project Manager for Software Quality Assurance.
 
 
 

<< Previous      Next >>


Microsoft   |   Windows   |   Visual Studio   |   Follow us on Twitter