1
votes

I'm working on a demo MVVM project where I have a WPF MainWindow with a ViewModel that needs to coordinate the creation and hosting of different UserControls. If the ViewModel is not supposed to have any part of WPF elements I'm not sure how to go about doing this. I know this is a rather broad design question but I'm new to WPF/TDD and I'm having difficulty seeing a clear path as to how to create and bind a UserControl to a ViewModel without having some of the create and bind code IN the ViewModel.

From what I've read exposing a UserControl property in the MainViewModel that binds to a ContentControl is not the way to go. How can I abstract away the creation and binding of UserControls in my MainView model so I can test it?

Works but not testable:

<ContentControl Grid.Row="2" Content="{Binding UserControl}" />

public class MainWindowViewModel
{
    public void ShowHome()
    {
        SomeUserControl uc = new SomeUserControl();
        uc.DataContext = new SomeUserControlViewModel();
        UserControl = uc;
    }

    public void ShowKeypad()
    {
        SomeOtherUserControl uc = new SomeOtherUserControl();
        uc.DataContext = new SomeOtherUserControlViewModel();
        UserControl = uc;
    }

    public UserControl UserControl {get; private set;}
}



3

3 Answers

2
votes

simply use a DataTemplate. Let Wpf choose the View for you.

<ContentControl Grid.Row="2" Content="{Binding UserControl}" />

public class MainWindowViewModel
{
  public void ShowHome()
  {
    MyViewmodelChoosedInMain  = new SomeViewModel();
  }

 public void ShowKeypad()
 {
    MyViewmodelChoosedInMain  = new SomeOtherViewModel();
 }

//better use an Interface instead of object type ;)
//you also need to implement and call INotifyPropertyChanged of course
 public object MyViewmodelChoosedInMain {get; private set;}
}

//in your ResourceDictionary create the DataTemplates
<DataTemplate DataType="{x:Type SomeViewModel}">
    <MySomeViewmodelView />
</DataTemplate>
<DataTemplate DataType="{x:Type SomeOtherViewModel}">
    <MySomeOtherViewmodelView />
</DataTemplate>
1
votes

There are a couple of things you can do.

  1. I have created many projects where the view at startup creates controls where the visibility is set to Hidden. Then the VM creates/has a State property which defines the different states of the app. As that property is changed (via INotifyPropertyChanged) controls on the screen appear or hide themselves.

  2. Working with #1 or without, one can create commands that the view can process but which are initiated by the VM or elsewhere. Hence keeping separation of concerns.


#1

Define Enum

public enum OperationState
{
    Select = 1,
    Routine,
    Alignment,
    SerialNumber,
}

On VM define state

private OperationState _State;

public OperationState State
{
    get => _State;
    set { _State = value; OnPropertyChanged(nameof(State)); }
}

Set state as needed such as State = Select.

Have control(s) viewability based on state

<Control:AlignmentProcessing 
            ProjectContainer="{Binding CurrentContainer, Mode=TwoWay}"
            Visibility="{Binding State, Converter={StaticResource VisibilityStateConverter},
                                        ConverterParameter=Alignment}"/>

The above control will only be visible during the Alignment state.

Converter Code

/// <summary>
/// Take the current state, passed in as value which is bound, and check it against
/// the parameter passed in. If the names match, the control should be visible,
/// if not equal, then the control (window really) should be collapsed.
/// </summary>
public class OperationStateToVisibility : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (value != null) &&
               (parameter != null) &&
               value.ToString().Equals(parameter.ToString(), StringComparison.OrdinalIgnoreCase)
            ? Visibility.Visible : Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    => throw new NotImplementedException();

}

#2 Otherwise implement a commanding operation on the VM such as:

public ICommand ShowControl1 { get; set; }

Then on the View subscribe to the command (assuming VM holds the current VM):

    VM.ShowControl1 = new Commanding((o) =>
    {
        SomeUserControl uc = new SomeUserControl();
        uc.DataContext = new SomeUserControlViewModel();
        UserControl = uc;
    }

Then when the command is executed (from the VM) the view does its work.

 ShowControl1.Execute(null);

I provide a commanding example on my blog Xaml: MVVM Example for Easier Binding

0
votes

The view-model should not have any controls in it with the MVVM pattern. A view-model is simply a state of the data to be displayed.

For example:

  • Record(s) to display
  • Title
  • Image
  • Can Delete
  • Can Edit

In WPF, the controls can bind to these properties for it's display.

View-Models are view agnostic (they don't care which view is consuming it). And so no actual reference to UI controls would be in them.

To test the actual UI, you can write a "coded UI test". In the web app works, there is the Selenium framework that allowed you to write unit tests for interacting with UI components in a browser.

I'm pretty sure there is a similar framework it there for WPF UI testing.

Edit: There is a framework called Appium that allows you to write the integration tests between your UI and the underlying MVVM setup you have.

http://appium.io/docs/en/drivers/windows/