Alex Rosenfeld Alex Rosenfeld - 25 days ago 9
C# Question

One View Model for multiple buttons

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.

Answer

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=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.

Comments