1

I'm new to WPF and MVVM. I'm trying to sort out how to have a clean project structure where there are multiple buttons that launch different apps (like chrome, IE, notepad). Is it possible to have one ViewModel for multiple buttons?

I have started by using the code from here, and my attempt at a solution is to make the code from that link the view model base and have other view models for each button that extend said base.

Each one has a unique action:

    public override void ClickAction()
    {
        Process ieProcess = new Process();
        ieProcess.StartInfo.FileName = "IExplore.exe";
        ieProcess.Start();
    }

However, I'm unsure of the proper way to go about doing this. How do I create a DataContext with this approach? Any help is appreciated.

Community
  • 1
  • 1
xandermonkey
  • 2,775
  • 2
  • 22
  • 39
  • 1
    Why does each button have its own data context to start with? Unless you are in an items control, that doesn't sound right. – BradleyDotNET Nov 07 '16 at 17:16
  • Thanks for the response. Like I said, I'm new to this and having a hard time grasping the design pattern. Can you make a suggestion on what I should do differently/how to approach this? – xandermonkey Nov 07 '16 at 17:18
  • Two suggestions, in fact :) – BradleyDotNET Nov 07 '16 at 17:35
  • Fantastic. Clearly I need many suggestions :) Looking for as many as you've got. For me, this is not an easy design pattern to get used to! – xandermonkey Nov 07 '16 at 17:36

1 Answers1

5

You should have one View Model per View; not per item in your view (in this case, buttons). An easy way to do what you are describing would be to use the CommmandParameter property to pass in your exe names:

<Button Content="Launch IE" Command="{Binding LaunchAppCommand}" CommandParameter="IExplorer.exe"/>
<Button Content="Launch Notepad" Command="{Binding LaunchAppCommand}" CommandParameter="notepad.exe"/>
<Button Content="Launch Chrome" Command="{Binding LaunchAppCommand}" CommandParameter="chrome.exe"/>

With your command:

public ICommand LaunchAppComand {get; private set;}
...
public MyViewModel()
{
    LaunchAppCommand = new DelegateCommand(LaunchApp);
}
...
private void LaunchApp(object parameter)
{
     string processName = (string)parameter;
     Process launchProc = new Process();
     launchProc.StartInfo.FileName = processName;
     launchProc.Start();
}

To avoid hard coding all your buttons you could use an ItemsControl, which does set an individual data context for each template it creates. To do so, you need a collection of data classes, and a slightly different way to get to your command:

//ProcessShortcut.cs
public class ProcessShortcut
{
    public string DisplayName {get; set;}
    public string ProcessName {get; set;}
}

//MyViewModel.cs, include the previous code

//INotifyPropertyChanged left out for brevity
public IEnumerable<ProcessShortcut> Shortcuts {get; set;} 

public MyViewModel()
{
    Shortcuts = new List<ProcessShortcut>()
    {
        new ProcessShortcut(){DisplayName = "IE", ProcessName="IExplorer.exe"},
        new ProcessShortcut(){DisplayName = "Notepad", ProcessName="notepad.exe"},
        new ProcessShortcut(){DisplayName = "Chrome", ProcessName="chrome.exe"},
    };
}

//MyView.xaml
<Window x:Name="Root">
...
   <ItemsControl ItemsSource="{Binding Shortcuts}">
      <ItemsControl.ItemTemplate>
          <DataTemplate>
              <Button Content="{Binding DisplayName, StringFormat='{}Start {0}'}" 
                      Command="{Binding ElementName=Root, Path=DataContext.LaunchAppCommand}" 
                      CommandParameter="{Binding ProcessName}"/>
          </DataTemplate>
      </ItemsControl.ItemTemplate>
   </ItemsControl>
...
</Window>

Because an ItemsControl sets the DataContext inside the template to the bound item you need an ElementName binding to get to the command, and don't need to qualify access to members of ProcessShortcut. In the long run, this is the approach you usually want to take when you have repetitious controls like this.

BradleyDotNET
  • 57,599
  • 10
  • 90
  • 109
  • Thanks very much for your help. Definitely a great explanation and start for my learning process! I found an implementation of DelegateCommand online that seems sufficient, do you have any input/advice about this? It seems there is no Microsoft implementation of it. – xandermonkey Nov 07 '16 at 18:18
  • @AlexRosenfeld The online implementations are ususally fine; but their implemention of `CanExecuteChanged` is exceptionally inefficient if they use CommandManager.RequerySuggested (see http://stackoverflow.com/a/3408591/1783619). As long as you avoid that most of them are good – BradleyDotNET Nov 07 '16 at 18:22
  • Okay, thanks again! Currently there's something wrong with the click command binding, it's not triggering any of the LaunchAppCommand calls. I'll look into it for a while and post a follow up later if I can't get it. You've been very helpful – xandermonkey Nov 07 '16 at 18:23
  • @AlexRosenfeld Check for `System.Data` errors in your output; they should point you to where your binding syntax is wrong (also, make sure you set the DataContext of the view to your view model) – BradleyDotNET Nov 07 '16 at 18:24
  • I'm a bit confused, LaunchApp() is never called, but the buttons are bound to the LaunchAppCommand. How are these two related? Is it called behind the scenes somehow or am I missing something? – xandermonkey Nov 07 '16 at 18:29
  • @AlexRosenfeld Yes; but not as "behind the scenes" as you may think! You passed `LaunchApp` into the `DelegateCommand` that `LaunchAppCommand` is bound to. When you click the button, the `Execute` method of the command is called by the framework (really, the `Button` class; you can see it on reference source), which in turn calls the delegate you passed into it. Make sense? – BradleyDotNET Nov 07 '16 at 18:34
  • Hmm. I understand what you mean, makes sense. It's still not working for some reason. When I add debug breakpoints, none of them get hit when I click the buttons. – xandermonkey Nov 07 '16 at 18:53
  • The error I receive is:`System.Windows.Data Error: 40 : BindingExpression path error: 'LaunchAppCommand' property not found on 'object' ''MyView' (Name='Root')'. BindingExpression:Path=LaunchAppCommand; DataItem='MyView' (Name='Root'); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')` – xandermonkey Nov 07 '16 at 18:59
  • @AlexRosenfeld Whoops, forgot to path through the datacontext on the items control version! Check my updated path – BradleyDotNET Nov 07 '16 at 19:33