How to drive the Form events in a other class? - c#

I desire the following:
When clickee a button on the form, I want to handle the events are another class. Thus, the form contains only controls.
It's almost like a MVC pattern: Controller I have a class, and a class RegistrarTrabajador (Model). When controller detects an event of the form, passes the task to the model.
Here the Controller class and the form:
Controller:
namespace RegistroDeUsuarios
{
public class Controller
{
private MainWindow vista;
private RegistrarTrabajador modelo;
public Controller()
{
}
public Controller(MainWindow vista, RegistrarTrabajador modelo)
{
this.vista = vista;
this.modelo = modelo;
}
public void btnRegistrar_Click(Object sender, RoutedEventArgs e)
{
Trabajador trabajador = new Trabajador();
trabajador.setPrimerNombre(vista.txtPrimerNombre.Text);
trabajador.setSegundoNombre(vista.txtSegundoNombre.Text);
trabajador.setPrimerApellido(vista.txtPrimerApellido.Text);
trabajador.setSegundoApellido(vista.txtSegundoApellido.Text);
trabajador.setRangoTrabajador(vista.cboRangoTrabajador.SelectedItem.ToString());
trabajador.setFechaNacimiento(vista.txtFechaNacimiento.Text);
modelo.registrarTrabajador(trabajador);
}
public void btnNuevo_Click(Object sender, RoutedEventArgs e)
{
vista.clean();
}
public void btnSalir_Click(Object sender, RoutedEventArgs e)
{
//Application.Current.Shutdown();
}
}
}
GUI:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
cboRangoTrabajador.Items.Add("Administrador");
cboRangoTrabajador.Items.Add("Vendedor");
cboRangoTrabajador.Items.Add("Contador");
cboRangoTrabajador.Items.Add("Tecnico Mantenimiento");
cboRangoTrabajador.Items.Add("Programador");
cboRangoTrabajador.Items.Add("Analista");
cboRangoTrabajador.SelectedIndex = 0;
}
public void setControlador(Controller controlador)
{
controlador.btnRegistrar_Click(controlador,new RoutedEventArgs());
controlador.btnNuevo_Click(controlador,new RoutedEventArgs());
controlador.btnSalir_Click(controlador,new RoutedEventArgs());
}
public void clean()
{
txtPrimerNombre.Clear();
txtSegundoNombre.Clear();
txtPrimerApellido.Clear();
txtSegundoApellido.Clear();
txtFechaNacimiento.Clear();
cboRangoTrabajador.SelectedItem = "Administrador";
txtPrimerNombre.Focus();
}
}

You don't use MVC in WPF. You use Model-View-ViewModel (MVVM)
And
you don't create or manipulate UI elements in procedural code in WPF. That's what XAML is for.
Please read about DataBinding,
things like this:
trabajador.setPrimerNombre(vista.txtPrimerNombre.Text);
trabajador.setSegundoNombre(vista.txtSegundoNombre.Text);
are horrible and should NEVER be done in WPF.
Also, your code smells like crappy java. Instead of methods like setPrimerNombre() you should really use Properties. WPF has support for two way databinding to properties, so you don't need to do all this piping manually.
To make this clear, here is a small example:
XAML:
<StackPanel>
<TextBox Text="{Binding Model.LastName}"/>
<TextBox Text="{Binding Model.FirstName}"/>
<Button Content="Registrar" Click="Registrar_Click"/>
<Button Content="Clear" Click="Clear_Click"/>
</StackPanel>
Code Behind:
public class MainWindow: Window
{
public MainViewModel ViewModel { get { return DataContext as MainViewModel; } }
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
public void Registrar_Click(object sender, RoutedEventArgs e)
{
ViewModel.Registrar();
}
public void Clear_Click(object sender, RoutedEventArgs e)
{
ViewModel.Clear();
}
}
ViewModel:
public class MainViewModel: ViewModelBase //You should have some ViewModelBase implementing INotifyPropertyChanged, etc
{
private Trabajador _model;
public Trabajador Model
{
get { return _model; }
set
{
_model = value;
NotifyPropertyChange("Model");
}
}
public void Registrar()
{
DataAccessLayer.Registrar(Model);
}
public void Clear()
{
Model = new Trabajador();
}
}
Model:
public class Trabajador: ModelBase //ModelBase Should also implement INotifyPropertyChanged
{
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
NotifyPropertyChanged("LastName");
}
}
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
NotifyPropertyChanged("FirstName");
}
}
//... And so on.
}

Not sure to have fully understand what you mean but i think you want to know what it is the best way of building a WPF application in layers.
If that's right then MVVM pattern is definitly what you are looking for. Here is a great link to understand how it works and to begin to play with it!

Related

How to change the value of MVVM property in CodeBehind

I have been trying to solve the following problem for a very long time but unfortunately, I am unable to get it fixed.
I have a button which I want to disable it in another page .cd
This is how my code looks like:
<StackLayout>
<Button Text="Click" IsEnabled="{Binding IsButtonEnabled}" Command="{Binding OnEnabledButtonClicked}"/>
</StackLayout>
public class MainPageViewModel : BaseViewModel
{
bool _isButtonEnabled;
public bool IsButtonEnabled
{
get => _isButtonEnabled;
set
{
_isButtonEnabled = value;
OnPropertyChanged(nameof(IsButtonEnabled));
}
}
public Command OnEnabledButtonClicked
{
get
{
return new Command( () =>
{
IsButtonEnabled = true;
}
}
}
}
And this is the class where I want to change the value of VM's button.
public class Page1 {
class page1() {
InitializeComponent();
}
public void OnDisabledButtonClicked(object sender, EventArgs e) {
/// IsButtonEnabled = false;
}
}
I have already tried different ways but still no result.
It would be a big help for me if someone provides me a solution for it.
Thanks in advance
Before I gave you an answer, I would like to point out few things.
It's common to suffix your command with Command : EnabledButtonCommand.
I can see that your command is async while you don't await anything. It's bad.
Why would you want to set IsButtonEnabled in the code behind instead of in the method executed by the command (in the ViewModel) ?
Where do you set the DataContext ? Do you use Prism or anything else to associate the ViewModel to your page's DataContext ? If you don't, you need to do this :
public class MyPage()
{
private MyViewModel _viewModel = new MyViewModel();
public MyPage()
{
InitializeComponent();
DataContext = _viewModel;
}
public void OnDisabledButtonClicked(object sender, EventArgs e)
{
_viewModel.IsButtonEnabled = false;
}
}
If your ViewModel was set in Xaml or elsewhere (while navigating, with Prism, etc)
public class MyPage()
{
private MyViewModel _viewModel;
public MyPage()
{
InitializeComponent();
_viewModel = DataContext as MyViewModel;
}
public void OnDisabledButtonClicked(object sender, EventArgs e)
{
_myViewModel.IsButtonEnabled = false;
}
}
A last word : if a button is not enabled, the command/click event won't be available to user until the button is enabled again.
You can test that with a button with its command binded to the given command and another button with IsEnabled binded to your boolean.
The code I gave may have things wrong as I answer in browser without using an EDI.

Handling Backend Events in UI class

EDIT: I have updated this with the two methods recommended
I am writing a simple custom PI (OSISoft) data viewer. I have two classes, one for the UI and one for the PI server interactions/program logic. The property for the data to be displayed has an event that fires when the property is changed. How do I get that change to propagate over to the UI class so the associated text box will automatically refresh?
Original code:
namespace PIViewer {
public partial class MainWindow : Window
{
ProgLogic logic;
public MainWindow() {
InitializeComponent();
logic = new ProgLogic();
InitializeValues();
}
private void InitializeValues() {
logic.SomeValue = logic.GetValFromServer(valueTag);
}
private void TextBoxSomeValue(object sender, TextChangedEventArgs e) {
// ??? something happens here?
}
}
public class ProgLogic {
private int someValue;
public event System.EventHandler SomeValueChanged;
protected void OnSomeValueChanged()
{
SomeValueChanged?.Invoke(this, EventHandlerArgs e);
}
public int SomeValue {
get => someValue;
set {
someValue = value;
OnSomeValueChanged();
}
}
public int GetValFromServer(string valueTag) {
int piValue;
piValue = PISDKMethodToGetServerValue(valueTag);
return piValue;
}
}
}
Method 1: Events
namespace PIViewer {
public partial class MainWindow : Window
{
ProgLogic logic;
public MainWindow() {
InitializeComponent();
logic = new ProgLogic();
logic.SomeValueChanged += Logic_SomeValueChanged;
InitializeValues();
}
private void Logic_SomeValueChanged(int obj) {
TextBoxSomeValue.Text = obj.toString();
}
private void InitializeValues() {
logic.SomeValue = logic.GetValFromServer(valueTag);
}
private void TextBoxSomeValue_TextChanged(object sender, TextChangedEventArgs e) {
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
logic.SomeValueChanged -= Logic_SomeValueChanged;
}
}
public class ProgLogic {
private int someValue;
public event Action<int> SomeValueChanged;
public virtual void OnSomeValueChanged(int newValue) {
SomeValueChanged?.Invoke(newValue);
}
public int SomeValue {
get => someValue;
set {
someValue = value;
OnSomeValueChanged(value);
}
}
public int GetValFromServer(string valueTag) {
int piValue;
piValue = PISDKMethodToGetServerValue(valueTag);
return piValue;
}
}
}
Method 2: MVVM pattern
MainWindow.xaml:
<Window
Closing="Window_Closing"
Title="My App">
<TextBox x:name="TextBoxSomeValue" text="{binding SomeValue, UpdateSourceTrigger=PropertyChanged}" />
</Window>
The important part here is the binding parameter in the text field of the TextBox definition, which points to the PropertyChangedEventHandler.
C# code:
namespace PIViewer {
public partial class MainWindow : Window
{
ProgLogic logic;
public MainWindow() {
InitializeComponent();
logic = new ProgLogic();
InitializeValues();
}
private void InitializeValues() {
logic.SomeValue = logic.GetValFromServer(valueTag);
}
private void TextBoxSomeValue_TextChanged(object sender, TextChangedEventArgs e) {
// run some other code when the text box updates
}
}
public class ProgLogic : INotifyPropertyChanged {
private int someValue;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public int SomeValue {
get => someValue;
set {
someValue = value;
OnPropertyChange("SomeValue")
}
}
public int GetValFromServer(string valueTag) {
int piValue;
piValue = PISDKMethodToGetServerValue(valueTag);
return piValue;
}
}
}
ProgLogic now implements INotifyPropertyChanged, which notifies the View of property changes, so that Bindings are updated.
I see you are heading the right way with C# event system. One thing I would change is event type from System.EventHandler to System.Action<int>. Even though people tend to propagate extending System.EventArgs class and writing custom delegates for handling events, using System.Action<T> is much easier to grasp for beginner.
So let's go with System.Action<int> example now. First, let's change ProgLogic class to be more like this:
public class ProgLogic
{
public event Action<int> SomeValueChanged;
//
// your other code goes here
//
private void OnSomeValueChanged(int newValue)
{
SomeValueChanged?.Invoke(newValue);
}
}
Now, you need to subscribe to the earlier written event in MainWindow class. So we do that as early as possible - in the constructor of MainWindow:
public MainWindow()
{
InitializeComponent();
logic = new ProgLogic();
logic.SomeValueChanged += OnSomeValueChanged;
InitializeValues();
}
Then, you describe your logic in the OnSomeValueChanged callback method, like:
private void OnSomeValueChanged(int newValue)
{
TextBoxSomeValue.text = newValue.ToString();
}
Make sure you unsubscribe from the event once MainWindow is getting destroyed to prevent memory leakage. This is just bare-bones for whole logic. I've left some space for interpretation. ;)
I'm not sure if I'm understanding the main point of your question but if you want to create a new value and have that value saved as the default value then you should create a string in your application setting and call on it on text changed.
At the top of your visual2019, in the menu options. open the debug menu and at the bottom you will see ("Your project name" + properties)
2.You will be brought into a new window with menu options on the left, go to the settings.
3.Create a string and set the value to "Some random text"
Note: In the example I placed one text box in front of the other, though this in not a great method it will prevent the text from appearing as a double or drawing a blank
Settings String Example
xaml
<Window x:Class="SaveNewText.MainWindow"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBox x:Name="DefaultText" Height="250" Width="250"
Background="Transparent"
Foreground="Black" MouseDown="TextBlock_MouseDown" IsReadOnly="True"/>
<TextBox x:Name="NewText" Height="250" Width="250" Background="Transparent"
Foreground="Black" TextChanged="NewText_TextChanged"/>
</Grid>
</Window>
xaml.cs
namespace SaveNewText
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DefaultText.Text = Properties.Settings.Default.TextString;
}
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
NewText.Focus();
}
private void NewText_TextChanged(object sender, TextChangedEventArgs e)
{
Properties.Settings.Default.TextString = NewText.Text;
Properties.Settings.Default.Save();
DefaultText.Text = Properties.Settings.Default.TextString;
}
}
}

Binding content from other files in XAML

So I planning to bind label from two files or more, because I place the label and the cs file in separate way. For example:
SettingServicesPhone.xaml
<Label x:Name="sipLoginStatus"
Width="106"
Height="27"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="{Binding SipLoginStatusContent}"
FontSize="13" />
For the SettingServicePhone.xaml.cs I declared public String sipLoginStatusContent;
And I use Settings.xaml and Setting.xaml.cs as a container of all functions.
I've declared public static SettingsServicesPhone setCall = new SettingsServicesPhone(); on Setting.xaml.cs. And also write get set.
public String SipLoginStatusContent
{
get { return setCall.sipLoginStatusContent; }
set
{
if (setCall.sipLoginStatusContent != value)
{
setCall.sipLoginStatusContent = value;
OnPropertyChanged("SipLoginStatusContent"); // To notify when the property is changed
}
}
}
And here the example of onclick button that I stated on Settings.xaml.cs
public void applyBtn_Click(object sender, RoutedEventArgs e)
{
SipLoginStatusContent = "Logging In";
}
It's work fine if I included them in one file. But seems like it doesn't running if I make it separate. Am I doing it wrong way? Thank you.
Set the DataContext of the window where the Label is defined to an instance of the class where the SipLoginStatusContent property is defined:
public partial class Settings : Window
{
public static SettingsServicesPhone setCall = new SettingsServicesPhone();
public Settings()
{
InitializeComponent();
DataContext = this; //<--
}
public String SipLoginStatusContent
{
get { return setCall.sipLoginStatusContent; }
set
{
if (setCall.sipLoginStatusContent != value)
{
setCall.sipLoginStatusContent = value;
OnPropertyChanged("SipLoginStatusContent"); // To notify when the property is changed
}
}
}
public void applyBtn_Click(object sender, RoutedEventArgs e)
{
SipLoginStatusContent = "Logging In";
}
}

Interact between Model and ViewModel of different UserControls without violating MVVM

EDIT: Added concrete example to clarify what I trying to achieve.
Here is application scheme:
To make code simpler, I will use trivial Messenger class instead of event aggregator from Prism. Tuple contains Id and string payload.
public static class Messenger
{
public static event EventHandler<Tuple<int, string>> DoWork;
public static void RaiseDoWork(int id, string path)
{
DoWork?.Invoke(null, new Tuple<int, string>(id, path));
}
}
Model instance subscribe to messenger for knowing when to start work (if Id correct), and notify view-model when work finished.
public class Model
{
public int id;
public Model(int id)
{
this.id = id;
Messenger.DoWork += (sender, tuple) =>
{
if (tuple.Item1 != this.Id)
{
return;
}
var result = tuple.Item2 + " processed with id " + this.id;
this.OnWorkCompleted(result);
};
}
public event EventHandler<string> WorkCompleted;
private void OnWorkCompleted(string path)
{
this.WorkCompleted?.Invoke(null, path);
}
}
UserControlResult is responsible for payload processing and result output. To make code simpler, lets just trace output instead of putting it on UI. So XAML will be default.
Code-behind:
public partial class UserControlResult : UserControl
{
private ResultViewModel viewModel;
public UserControlResult()
{
this.InitializeComponent();
}
public void Init(int id)
{
this.viewModel = new ResultViewModel(id);
this.DataContext = this.viewModel;
}
}
View-model:
public class ResultViewModel
{
private Model model;
public ResultViewModel(int id)
{
this.model = new Model(id);
this.model.WorkCompleted += path =>
{
Trace.WriteLine(path);
};
}
}
UserControlButtons contains buttons, one of them should start processing of model in UserControlResult via messenger. To make code simpler, lets omit command implementation and just show its handler.
Code-behind:
public partial class UserControlButtons : UserControl
{
private ButtonsViewModel viewModel;
public UserControlButtons()
{
this.InitializeComponent();
}
public void Init(int id)
{
this.viewModel = new ButtonsViewModel(id);
this.DataContext = this.viewModel;
}
}
View-model:
public class ButtonsViewModel
{
private int id;
public ButtonsViewModel(int id)
{
this.id = id;
}
// DelegateCommand implementation...
private void StartWorkingCommandHandler()
{
Messenger.RaiseDoWork(this.id, "test path");
}
}
UserControlParent contains both UserControlResult and UserControlButtons. His only role is to pass Id to them, so he doesn't even need view-model.
Xaml:
<StackPanel>
<uc:UserControlResult x:Name="UserControlResult" />
<uc:UserControlButtons x:Name="UserControlButtons" />
</StackPanel>
Code-behind:
public partial class UserControlParent : UserControl
{
public UserControlParent()
{
this.InitializeComponent();
}
public void Init(int id)
{
this.UserControlResult.Init(id);
this.UserControlButtons.Init(id);
}
}
And finally MainWindow contains two instances of UserControlParent. Its role to assign them different Ids.
Xaml:
<StackPanel>
<uc:UserControlParent x:Name="UserControlParent1" />
<uc:UserControlParent x:Name="UserControlParent2" />
</StackPanel>
Code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
this.UserControlParent1.Init(111);
this.UserControlParent2.Init(222);
}
}
This will work: pressing button in UserControlButtons will start working in UserControlResult model, and both UserControlParent will working correct and independend thanks to Id.
But I believe that this chain of invoking Init methods is violates MVVM because code-behind (which is View in MVVM) should not know anything about Id value (which is relative to Model in MVVM). Talking that, I'm sure that Id is not part of view-model, because it doesn't have any presentation in UI.
How can I pass Id value from top window to "deepest" view-models without violating MVVM?
Original Question
Here is WPF application consisting from 3 UserControls:
UserControl3 is a part of UserControl2 content. I keep MVVM during developing and using Prism.
I need to invoke method of custom class (which is model in terms of MVVM) in UserControl3 from view-model of UserControl1. The restriction that custom class can't be singleton. I suppose to do it one of the following way:
Using event aggregator from Prism. UserControl1 view-model is publisher and UserControl3 model is subscriber. For this I'll need to create unique Id in Window and pass it to UserControl1 and UserControl3.
Creating service instance in Window and pass it to UserControl1 and UserControl3. Then UserControl1 will just invoke method of this instance.
Window pass UserControl2 instance to UserControl1. View-model in UserControl1 will just invoke method of UserControl2, which will invoke method of UserControl3 and so on.
It seems like 2 and 3 approaches violates MVVM. How would you resolve this situation?
I would use option 1. I use MVVM Light to send a message and whoever receives that specific message will fire off the service method. Loosely coupled.
I think I achieved truly MVVM implementation shown in simplified example below. Special thanks to Ed Plunkett's comment and Nikita's answer.
First, I don't need to pass unique Ids anymore. For identification of different ParentViewModel instances, I just pass them different Messenger instances (which replaces Prism's EventAggregator for the sake of simplicity):
internal class Messenger
{
public event EventHandler<string> DoWork;
public void RaiseDoWork(string path)
{
this.DoWork?.Invoke(this, path);
}
}
Second, it seems like in my particular case Model should not worry about Messenger's DoWork event. As soon as this event raised in one view-model (ButtonsViewModel), it is more appropriate for this event to be consumed by another view-model (ResultViewModel) rather than by Model itself. So Model simplified too:
internal class Model
{
public string Process(string input)
{
return input + " processed!";
}
}
Below demonstrated all view-models "from top to bottom".
internal class MainViewModel
{
private readonly Messenger eventAggregator1 = new Messenger();
private readonly Messenger eventAggregator2 = new Messenger();
public MainViewModel()
{
this.ParentViewModel1 = new ParentViewModel(this.eventAggregator1);
this.ParentViewModel2 = new ParentViewModel(this.eventAggregator2);
}
public ParentViewModel ParentViewModel1 { get; }
public ParentViewModel ParentViewModel2 { get; }
}
internal class ParentViewModel
{
public ParentViewModel(Messenger eventAggregator)
{
this.ButtonsViewModel = new ButtonsViewModel(eventAggregator);
this.ResultViewModel = new ResultViewModel(eventAggregator);
}
public ButtonsViewModel ButtonsViewModel { get; }
public ResultViewModel ResultViewModel { get; }
}
internal class ButtonsViewModel
{
private readonly Messenger eventAggregator;
public ButtonsViewModel(Messenger eventAggregator)
{
this.eventAggregator = eventAggregator;
this.StartCommand = new DelegateCommand(this.StartProcessing);
}
public DelegateCommand StartCommand { get; }
private void StartProcessing()
{
this.eventAggregator.RaiseDoWork("test path");
}
}
internal class ResultViewModel : ViewModelBase
{
private readonly Model model = new Model();
private string textValue;
public ResultViewModel(Messenger eventAggregator)
{
eventAggregator.DoWork += (sender, s) => this.DoWorkHandler(s);
}
public string TextValue
{
get { return this.textValue; }
set { this.SetProperty(ref this.textValue, value); }
}
private void DoWorkHandler(string s)
{
var result = this.model.Process(s);
this.TextValue = result;
}
}
Note that in ResultViewModel I replaced Trace.WriteLine with actual screen output (because now strings are without Id, so trace output the same). ViewModelBase just implements INotifyPropertyChanged.
Below demonstrated content part of all views "from top to bottom".
<!-- MainWindow.xaml -->
<StackPanel Orientation="Horizontal">
<views:UserControlParent DataContext="{Binding ParentViewModel1}" />
<views:UserControlParent DataContext="{Binding ParentViewModel2}" />
</StackPanel>
<!-- UserControlParent.xaml -->
<StackPanel>
<local:UserControlResult DataContext="{Binding ResultViewModel}" />
<local:UserControlButtons DataContext="{Binding ButtonsViewModel}" />
</StackPanel>
<!-- UserControlButtons.xaml -->
<Grid>
<Button Content="Test" Command="{Binding StartCommand}" />
</Grid>
<!-- UserControlResult.xaml -->
<Grid>
<TextBlock Text="{Binding TextValue}" />
</Grid>
And finally this two worlds are connected in App.xaml.cs:
private void App_OnStartup(object sender, StartupEventArgs e)
{
new MainWindow { DataContext = new MainViewModel() }.Show();
}
Seems like MVVM, but any remarks are welcome.

In WPF, how can a Command's CanExecute method gain visibility of other UI elements?

I've been using WPF for a while but I'm new to Commands, but would like to start using them properly for once. Following a code example, I've established a separate static Commands class to hold all of my commands, and it looks like this.
public static class Commands
{
public static RoutedUICommand OpenDocument { get; set; }
static Commands()
{
OpenDocument = new RoutedUICommand("Open Document", "OpenDocument", typeof(Commands));
}
public static void BindCommands(Window window)
{
window.CommandBindings.Add(new CommandBinding(OpenDocument, OpenDocument_Executed, OpenDocument_CanExecute));
}
private static void OpenDocument_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// Should be set to true if an item is selected in the datagrid.
}
private static void OpenDocument_Executed(object sender, ExecutedRoutedEventArgs e)
{
}
}
My problem is that although the command is going to be bound to a Button control in MainWindow.xaml, the OpenDocument_CanExecute method needs to look at a DataGrid in MainWindow.xaml to see if an item is selected.
How can I wire things up such that the method can see the DataGrid?
SOLUTION
Inspired by Ken's reply (thanks again!), I put the following in place, which works perfectly.
MainWindow.xaml.cs
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
Loaded += delegate
{
DataContext = ViewModel.Current;
Commands.BindCommands(this);
};
}
}
ViewModel.cs
public class ViewModel
{
private static ViewModel _current;
public static ViewModel Current
{
get { return _current ?? (_current = new ViewModel()); }
set { _current = value; }
}
public object SelectedItem { get; set; }
}
Commands.cs
public static class Commands
{
public static RoutedUICommand OpenDocument { get; set; }
static Commands()
{
OpenDocument = new RoutedUICommand("Open Document", "OpenDocument", typeof(Commands));
}
public static void BindCommands(Window window)
{
window.CommandBindings.Add(new CommandBinding(OpenDocument, OpenDocument_Executed, OpenDocument_CanExecute));
}
private static void OpenDocument_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = ViewModel.Current.SelectedItem != null;
}
private static void OpenDocument_Executed(object sender, ExecutedRoutedEventArgs e)
{
}
}
ICommand implementations work best in the MVVM pattern:
class ViewModel : INotifyPropertyChanged {
class OpenDocumentCommand : ICommand {
public bool CanExecute(object parameter) {
return ViewModel.ItemIsSelected;
}
public OpenDocumentCommand(ViewModel viewModel) {
viewModel.PropertyChanged += (s, e) => {
if ("ItemIsSelected" == e.PropertyName) {
RaiseCanExecuteChanged();
}
};
}
}
private bool _ItemIsSelected;
public bool ItemIsSelected {
get { return _ItemIsSelected; }
set {
if (value == _ItemIsSelected) return;
_ItemIsSelected = value;
RaisePropertyChanged("ItemIsSelected");
}
}
public ICommand OpenDocument {
get { return new OpenDocumentCommand(this); }
}
}
Obviously, I left out a whole bunch of stuff. But this pattern has worked well for me in the past.
why even implement a command if you are tightly coupling it to UI implementation? Just respond to datagrid.SelectionChanged and code in what supposed to happen.
Otherwise, put it in the ViewModel. Have the ViewModel monitor it's state and evaluate when CanExe is true.
Edit
On the other hand, you can pass a parameter to your command, as well as Exe() & CanExe() methods
//where T is the type you want to operate on
public static RoutedUICommand<T> OpenDocument { get; set; }
If you are doing an MVVM solution, this would be the perfect time to implement a publish / subscribe aggregator that allows controls to "talk" to each other. The gist behind it is that the datagrid would publish an event, 'Open Document'. Subsequent controls could subscribe to the event and react to the call to 'Open Document'. The publish / subscribe pattern prevents tightly coupling the datagrid and the control. Do some searches for event aggregators and I think you'll be on your way.

Categories

Resources