views:

378

answers:

2

I am having trouble with the MVVM pattern and Commands in my WPF app. The problem is not so much the MVVM pattern, but more the stuff that is going on on my GUI. I'll explain the situation:

My app can DoStuff to some files. I have a class with a function DoStuff(int limit). My user user interface has the following items:

  • A Button DoStuffBtn to start parsing.
  • A TextBox LimitTxt to fill in a limit.
  • A CheckBox LimitChk to enabled or disable the limit.

When you would "uncheck" LimitChk, then LimitTxt.Text = "" and LimitTxt.IsEnabled = false. When you would "check" LimitChk, then LimitTxt.IsEnabled = false again, but the text remains empty until you fill something in.

I have read many tutorials on Commands in WPF and MVVM but I just can't seem to pour my case into that mold. The example I gave is actually just a small part of my UI, but I can't seem to do this nicely either.

I keep running into questions like:

  • Do I need two Commands for LimitChk (enable and disable) or just one (toggle)?
  • If I bind an int to LimitTxt, what happens if I make it empty and disable it?
  • Is it a clean way to just use DoStuff(Int32.Parse(LimitTxt.Text)) when DoStuffBtn is pressed?
  • If I use two commands on LimitChk, what happens with the CanExecute() function of ICommand that determines whether LimitChk is enabled?

So the main question is: How would the situation I described fit into a nice pattern using Commands in WPF?

Some links on WPF, Commands and MVVM i've looked at:


What I understand so far is that I have to keep as much as possible out of the UI. Even stuff like UI influencing the UI. I.e. unchecking LimitChk disables LimitText. Still, I think I should keep a difference between UI related information and actions and stuff that actually has to do with the actual work that has to be done.

+1  A: 

I think you're getting confused... you don't need any commands here, you can just use bindings.

  • Do I need two Commands for LimitChk (enable and disable) or just one (toggle)?

You need none. Just create a LimitEnabled property in your ViewModel, and bind the CheckBox to it (IsChecked="{Binding LimitEnabled}")

  • If I bind an int to LimitTxt, what happens if I make it empty and disable it?

Disabling it has no effect. If you make the TextBox empty, the binding will fail because an empty string can't be converted to an int (at least not with the default converter)

  • Is it a clean way to just use Parse(Int32.Parse(LimitTxt.Text)) when ParseBtn is pressed?

You don't need to. Just create a Limit property in your ViewModel, and bind the TextBox to it. You might want to add an ExceptionValidationRule to the Binding so that it highlights invalid input.

The button is not necessary, the parsing will be done automatically when the TextBox loses focus (if you use the default UpdateSourceTrigger). If you want to customize the way it's parsed, you can create a custom converter to use in the binding.

Thomas Levesque
About the limit enabled, it is not just a property that needs to be set, but some stuff on the UI needs to change when I check LimitChk. Or should I make a property listener to do that when LimitEnabled changes? It might not have been a good idea for me to choose Parse as the thing for the button while also talking about int32.Parse. The Parse on the button is actually some other Parse. I'll replace it with DoStuff.
Matthijs Wessels
+1  A: 

Just some high level thoughts, leaving out superfluous stuff like Color and alignment attributes, WrapPanels, etc.

Your ViewModel has a a couple properties:

public bool? LimitIsChecked { get; set; }
public bool LimitTextIsEnabled { get; set; }  //to be expanded, below
public ICommand ParseCommand { get; private set; } // to be expanded, below
public string LimitValue { get; set; } // further explanation, below

Your XAML has CheckBox and TextBox definitions something like:

<CheckBox Content="Limit Enabled" IsChecked="{Binding LimitIsChecked}" />
<TextBox Text="{Binding LimitValue}" IsEnabled="{Binding LimitIsEnabled}" />
<Button Content="Parse" Command="{Binding ParseCommand}" />

You'll want to initialize ParseCommand something like this:

this.ParseCommand = new DelegateCommand<object>(parseFile);

Now, let's fill in that LimitTextIsEnabled property too:

public bool LimitTextIsEnabled {
    // Explicit comparison because CheckBox.IsChecked is nullable.
    get { return this.LimitIsChecked == true; }
    private set { }
}

Your parseFile method would then pass the value of the LimitValue property to the logic doing the actual parsing.

I declared the LimitValue property as string here to avoid cluttering up the code with an explicit converter, or other validation code. You could choose to handle that "LimitValue is a valid int" verification/conversion in several different ways.

Of course, I haven't implemented this in its entirety, but I wanted to outline a pattern where you are not using Commands to update the state of the other widgets. Instead, bind those attributes to properties that are managed in your ViewModel.

JMD
Hmm interesting solution. So if I understand correctly, the stuff on the UI that influences other stuff on the UI (i.e. the state of the checkbox influencing the state of the textbox) should go through the viewmodel. I'll have to look into what that DelegateCommand<object> thing is though. Oh and, why does the LimitTextIsEnabled property need a private set? Thanks
Matthijs Wessels
Yep, you have the general idea there.LimitTextIsEnabled doesn't *need* a private set, but it doesn't need a public set either. :)`DelegateCommand<T>` came from here: http://msdn.microsoft.com/en-us/library/cc707894.aspx
JMD