Showing posts with label WPF. Show all posts
Showing posts with label WPF. Show all posts

Saturday, August 18, 2018

WPF: Ways to Set DataContext

Something that I've struggled when implementing Model-View-ViewModel design in WPF is how to specify DataContext so that I can bind control properties to view model properties. There are quite a few ways to do this, and each have trade-offs in terms of Blendability (design-time functionality in Blend or Visual Studio Designer) and maintainability.

Set in Code-Behind

When I first started doing MVVM in WPF, I used code behind to set the DataContext in all of my windows and pages:


public MyControl()
{
    InitializeComponent();
    DataContext = new MyViewModel();
}


Blendability with this approach is poor. The designer can't actually see that your DataContext is a MyViewModel instance. AFAIK, designers don't run that constructor at design time. (If they do, I'd really be surprised because I've never seen it happen.) So you don't get auto-completion, go-to-definition, or anything else in the designer.

But there is an advantage to the approach: you can design view models that take constructor parameters. This makes it easy to write view models that wrap around model objects as you can pass that model to the view model's constructor.

Set in XAML Using Locator Instance


After a few years, I started using MVVM Light instead of utilizing hand-rolled solutions for MVVM. Installing MVVM Light's full package (MvvmLight, not MvvmLightLibs) adds a few classes to your project. One of these is the ViewModelLocator class, which is responsible for instances of your view model classes. If you add an instance of this locator class to your application's resources in App.xaml, you can set your control's DataContext in XAML:


<UserControl
    x:Class="MyProject.MyControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    mc:Ignorable="d"
    DataContext="{Binding Source={StaticResource Locator}, Path=MyViewModel}">
</Code>


This approach offers far superior Blendability over the previous approach - you get auto-complete, extended error reporting, and design-time preview in Visual Studio Designer. This makes for a much faster feedback loop than running the program every time.

It's still possible to design your view models so that their constructors take parameters. However, this is effectively limited to dependency injection as the locator pattern makes it difficult to send a model object to the view model's constructor.

If you want, you can still load your view model with model-specific data. My typical methodology involves handling the control's Loaded event. The event handler can call a 'load' method on the view model, and you can get the view model instance either from the locator or by casting the value for the control's DataContext property to the correct type.

But if you use loading methods, expect to call them on a regular basis: the locator class can make the lifetime of your view models difficult to reason with. In other approaches, the life of the view model instance ends with the view that created it. But view models in the locator can live to the end of the application (if you keep a reference to each instance) or can change when you least expect it (if your locator's properties return a new instance every time).

You have to change the locator, the view XAML, and the view code-behind every time you want to use a different class for the view model. In my experience, it doesn't come up often, but it can still break unexpected parts of your application after making the change.

Set in XAML, Create New Instance in XAML


I recently learned about a third approach from a question on StackOverflow: Setting DataContext in XAML in WPF. The accepted answer from BradleyDotNET does something like this in the XAML:


<UserControl.DataContext>
    <vm:MyViewModel />
</UserControl.DataContext>


Like the previous method, you get auto-complete and errors during design time. And, if you assign a name to the view model instance, you can access it from code-behind without needing to cast anything. It just works, and it's an elegant solution as far as the code-behind goes.

There is a big downside: the view model class must have a default constructor. It's technically possible to get around it using a type converter, but you lose the ability to preview data, it leads to a lot of confusing code, and I wouldn't recommend it. Implementing a default constructor means having to inject dependencies through properties or put all of the DI code in the default constructor. This can lead to some messy code in your view model that can complicate your unit tests.

Set in XAML, Create New Instance in Code-Behind


While doing research for this article, I found that there's another way to assign a value to DataContext that involves writing some code-behind and XAML. But I don't recommend using it for reasons that will soon become apparent:

You can declare a property in the code-behind:


public MyViewModel ViewModel { get; } = new MyViewModel();


and use it in your XAML:


<Window x:Class="TestApp.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:TestApp"
    mc:Ignorable="d"
    Title="MainWindow"
    x:Name="Self"
    DataContext="{Binding ElementName=Self, Path=ViewModel}">


It seems like you would get design-time support with this approach, but this works almost as poorly at design-time as the code-behind approach. You can hover over property names and see their type, but you can't jump to their definition or preview the bindings in the designer. I understand why preview wouldn't work - the designer is not using an instance of the view model. But it seems weird that type definitions only partially work.

Another big downside is that I've never heard anyone talk about setting DataContext this way. It's a non-standard approach, and would probably receive some funny looks during a code review.

For those two reasons, I can't recommend actually using this hybrid approach. But it's kinda fun to think about.

Conclusion


That leaves me with three usable approaches for setting the DataContext for your windows, pages, and controls:

  • Set in Code-Behind
  • Set in XAML Using Locator Instance (the MVVM Light method)
  • Set in XAML, Create New Instance in XAML

They all have their benefits and problems, but if I had to choose one to use for the rest of my life, it would be MVVM Light's approach. With it, Blendability is excellent, code follows a commonly-used pattern, and you can still do dependency injection through view model constructors.

Friday, November 15, 2013

Getting Started with KSMVVM.WPF Part 2: Messaging

(This is part 2 of 2 in a series about getting started with KSMVVM.WPF)

At the end of Getting Started with KSMVVM.WPF, we successfully converted our SampleApplication to use ViewModels. However, one of our ViewModels still has UI-specific code in it. If we write and run automated ViewModel tests, testing FormViewModel.Submit will show a MessageBox. This is not good!

KSMVVM.WPF has a messaging component, and we'll use its BasicMessager class to move the call to MessageBox.Show out of FormViewModel.

Thursday, November 14, 2013

Getting Started with KSMVVM.WPF

KSMVVM.WPF is a 'kinda small' Model-View-ViewModel framework for Windows Presentation Foundation (WPF) that I released earlier in 2013. It's a little different than other micro-MVVM frameworks because it was specifically designed for migrating existing code-behind WPF applications to MVVM.

Some nice features of KSMVVM.WPF include:
  • Functionality to allow ViewModels to control program navigation (in a manner to allow for automated tests)
  • Lightweight, easy-to-use string-based messaging
  • Two ICommand implementations; a 'hack' one for existing apps, and a 'non-hack' one for new apps
This 'getting started' guide will illustrate use of KSMVVM.WPF in an existing WPF application. This guide assumes that you are using a recent version of Visual Studio (or VS Express) with built-in NuGet functionality. I'm actually using VS Express for Desktop 2013 RC for this tutorial.

The tutorial continues after the page break.

Thursday, September 12, 2013

Skippable - Skip UI-specific Code During .NET VM tests

KSMVVM.WPF, my "kinda small" MVVM framework for WPF, used to have a class named Skippable. Its purpose was simple: to allow the inclusion of UI-specific code inside of a View Model and have it be skippable for unit tests.



How to Use Skippable

Call Skippable.Do(func) in your View Model code and place UI-specific code in func.

Wrap your View Model unit test code in a using(Skippable.Skip) block.

Why Was It Removed?

Before I added a messaging class to KSMMVM.WPF, Skippable was the only built-in way of triggering a change in the UI that could not be prompted through binding alone. For example, calls MessageBox.Show() were intended to go inside of a Skippable() block until the program transitioned to a MVVM framework with messaging capability.

I realized that Skippable encouraged poor programming practices. MVVM is all about separation of concerns, and Skippable violates that separation by allowing 'View code' in the View Model. It also requires tests to know if something uses Skippable.

Skippable was meant to help programmers transition WPF code from code-behind to a proper MVVM framework, but it did a grave disservice to developers who 'stick' with KSMVVM.WPF.

Wednesday, July 31, 2013

Changing Control Visibility in WPF MVVM Apps

In WPF MVVM applications, you might need to show, hide, or collapse controls based on data from ViewModels. This is trivial for code-behind applications: name the control & manipulate its visibility directly when you need to. MVVM applications need a different approach. In this post, I'll outline two of the approaches that I have used.

If you use a different approach for manipulating Visibility in WPF MVVM apps, let me know!

Approach A - Introduce Visibility Properties to ViewModel

First, add a new property to your ViewModel. The type of this property needs to be System.Windows.Visibility. The setter for this property can be private; the view only needs the property's getter.

Second, bind the new property to the control's Visibility property.

The advantage to this approach is that you don't have to touch your View's code-behind. The disadvantage is that your ViewModel now contains presentation logic, which is bad for separation of concerns.

Approach B - Set Visibility in Code-Behind

Using this approach requires setting x:Name for your control. The idea is that you add code to code-behind that uses ViewModel values to determine the control's visibility.

If visibility does not change during the Page's lifetime, you can go ahead and set Visibility during Page initialization using ViewModel data.

If the visibility of your control can change during the lifetime of the page, you'll need to use messaging or events in your ViewModel to trigger updates to the page. The event handling (or message handling) code will set the Visibility of the control.

The code-behind will be larger (due to the Visibility-setting code and any additional event/message handlers that you add) and your Page will have an additional property (the control), but this allows you to manipulate control visibility without adding presentation-specific code to your ViewModel.

Thursday, May 16, 2013

Identifying WPF BackStack Entries

Frame.BackStack is a useful property in WPF applications: it's an IEnumerable for a frame's navigation history. In my experience, these back entries tend to be JournalEntry instances. JournalEntry.Name can help you find what Page is represented by the JournalEntry, but it is a little tricky to use in practice. Name can be one of four things:
  1. The attached Name attribute.
  2. Title.
  3. WindowTitle and the uniform resource identifier (URI) for the current page
  4. The uniform resource identifier (URI) for the current page.
(Source: JournalEntry.Name documentation on MSDN)

If you set x:Name for a page, the corresponding JournalEntry.Name will be that x:Name. Doing this allows you to (somewhat) reliably identify the JournalEntry's page.

Saturday, October 27, 2012

Rewriting WPF Code-Behind to MVVM

I wrote a code-behind WPF application. The app grew, and the maintainability of the app's GUI-related code became quite poor. Some data input pages used models for the DataContext, other pages had code-behind that read individual fields, and there was one page that used a 'custom model class' that was basically a view model without any logic.

After converting most of the code-behind to use MVVM instead, I found that my program was far easier to maintain than the code-behind version.

Choosing a MVVM Toolkit

The first step is to decide on a MVVM toolkit to use. I used a custom MVVM toolkit, but toolkits like MVVM Light Toolkit and Caliburn.Micro are available for use. WPF Apps With The Model-View-ViewModel Design Pattern explains much of what you need to know to write your own.

You also need to decide on how navigation occurs in your code. Magellan is one possible solution, and some MVVM toolkits also solve this problem. If you are writing your own, you should take a quick look at pre-existing toolkits to see how they handle navigation.

Converting Code-Behind to MVVM

The conversion process provides unique challenges because the structure of code-behind can be different for each page. Because each page will be slightly different for code-behind, this is an incomplete guide to converting code.

Most pages in my application used a Model as the DataContext, and the conversion process for most of those pages went like this:
  1. Create the view model (which should extend a ViewModel base class)
    1. Code-behind click handlers become custom commands (you can mostly copy code from the click handler into the custom command)
    2. Navigation calls are changed so that they don't use NavigationService
    3. Models & private fields in the code-behind become VM properties or private fields
    4. Named controls in the View become properties in the VM of an appropriate type (most likely String)
    5. If code behind changes IsEnabled or Visibility for a control, add a property of type bool (IsEnabled) or Visibility for each control changed in this way so that VM code can manipulate IsEnabled/Visibility for that control through data binding
  2. Change the page to use the view model as its data context (do not remove click handlers yet)
    1. Change the DataContext to use a view model instance (and add a private field for the model if necessary)
    2. Change all data bindings to match those of the VM 
    3. Remove model properties from the View's code-behind
    4. Replace any calls to the (now removed) model properties with calls to ViewModel properties
  3. Add bindings for IsEnabled or Visibility for controls that need them
  4. Test with an emphasis on data validation & property bindings
  5. Replace the click handlers with VM Command bindings in the Page's xaml file.
  6. Test with an emphasis on commands and navigation.

Sunday, August 26, 2012

WPF DataGrid and 'Random' ArgumentException

WPF's DataGrid class seems to have an odd problem. If you put a DataGrid in a ScrollViewer, and if you bind a collection of a type with a specific kind of GetHashCode() implementation, and if you edit data in the grid twice, an ArgumentException will likely be thrown by clicking the data grid a third time. I say "likely" because it sometimes didn't occur on the third attempt. Here's an example stack trace:



I could not find source code for InternalSelectedItemsStorage's constructor, but it probably calls GetHashCode() to generate a dictionary key. The DataGrid problem is resolved by changing the GetHashCode() implementation so that it gives the same return value even if the name or price of an object changes.

Here are two examples (one bad, one better) of GetHashCode() implementations.