my goal is filter a dataGrid with a textbox input, so i have textbox to filter a datagrid. for all the other commands i used one of these constractors in my **relayCommand ** as follows:
// Constructors
public RelayCommand(Action<object> action)
{
_execute = action;
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
now to do filter datagrid based on a textbox input i make a searchMethod in my viewModel as follows:
private ObservableCollection<Coach> searchMethod()
{
return CoachManage.GetCoachBySearch(TextToFilter);
}
and also a Command in my viewModel as follows:
public ICommand SearchCommand
{
get
{
if (_searchCommand == null)
{
_searchCommand = new RelayCommand(new Action<object>(searchMethod()));
}
return _searchCommand;
}
set
{
SetProperty(ref _searchCommand, value);
}
}
and finally my textbox binded to the property of the one in my viewModel in my View:
<TextBox x:Name="txtSearch"
Margin="7,3,3,3" Grid.Row="1" Grid.Column="1"
Text="{Binding TextToFilter, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextBoxStyle}"
/>
but the error says in following code:
*_searchCommand = new RelayCommand(new Action<object>(SearchCoach()));*
Method name expected
it seems that I should make new constructor in my relayCommand class which takes parameter to pass it in my Method. is that right and how could i do that?
You must register the delegate properly (the issue is not related to the constructor).
Here are some variations that all yield the same result, but more or less elegant. Some use the shortest form, method groups. Some use lambda expressions:
// Very verbose using method groups
var searchCommand = new RelayCommand(new Action<object>(SearchCoach));
// Very compact using method groups
var searchCommand = new RelayCommand(SearchCoach);
// Using a lambda expression
var searchCommand = new RelayCommand(commandParameter => SearchCoach(commandParameter));
// Using method groups
Action<object> commandDelegate = SearchCoach;
var searchCommand = new RelayCommand(commandDelegate);
// Using a lambda expression
Action<object> commandDelegate = commandParameter => SearchCoach(commandParameter);
var searchCommand = new RelayCommand(commandDelegate);
And many more, for example using an anonymous method.
If you want to use a parameterless void delegate you would have to add a corresponding constructor overload to your RelayCommand:
private Action _executeParameterless;
public RelayCommand(Action parameterlessAction)
{
_executeParameterless = parameterlessAction;
}
public void Execute(object commandParameter)
{
_execute?.Invoke(commandParameter);
_executeParameterless?.Invoke();
}
Related
The following code is working for usercontrols but not in the Mainwindows. Setting Focusable="True" for the mainwindow.
<Window.InputBindings>
<KeyBinding Modifiers="Ctrl" Key="S" Command="{Binding SaveCommand}" />
</Window.InputBindings>
private ICommand _saveCommand;
public ICommand SaveCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand(
param => this.SaveObject(),
param => this.CanSave()
);
}
return _saveCommand;
}
}
private bool CanSave()
{
return (Project != null);
}
private void SaveObject()
{
// Code here
}
Got fixed by using the below code from the link.
Keyboard shortcuts in WPF
public YourWindow() //inside any WPF Window constructor
{
...
//add this one statement to bind a new keyboard command shortcut
InputBindings.Add(new KeyBinding( //add a new key-binding, and pass in your command object instance which contains the Execute method which WPF will execute
new WindowCommand(this)
{
ExecuteDelegate = TogglePause //REPLACE TogglePause with your method delegate
}, new KeyGesture(Key.P, ModifierKeys.Control)));
...
}
Create a simple WindowCommand class which takes an execution delegate to fire off any method set on it.
public class WindowCommand : ICommand
{
private MainWindow _window;
//Set this delegate when you initialize a new object. This is the method the command will execute. You can also change this delegate type if you need to.
public Action ExecuteDelegate { get; set; }
//You don't have to add a parameter that takes a constructor. I've just added one in case I need access to the window directly.
public WindowCommand(MainWindow window)
{
_window = window;
}
//always called before executing the command, mine just always returns true
public bool CanExecute(object parameter)
{
return true; //mine always returns true, yours can use a new CanExecute delegate, or add custom logic to this method instead.
}
public event EventHandler CanExecuteChanged; //i'm not using this, but it's required by the interface
//the important method that executes the actual command logic
public void Execute(object parameter)
{
if (ExecuteDelegate != null)
{
ExecuteDelegate();
}
else
{
throw new InvalidOperationException();
}
}
}
I am trying to attach a command and a commandparameter to a textbox on return key but without success. The parameter is the current text in the same textbox.
<TextBox x:Name="txtSearch">
<TextBox.InputBindings>
<KeyBinding Command="{Binding SearchCommand}"
CommandParameter="{Binding Path=Text, ElementName=txtSearch}" Key="Return" />
</TextBox.InputBindings>
</TextBox>
Basically I want to execute the command when user clicks on return/enter key and pass as a parameter the current text in the textbox.
I have found this link where it is said that in .NET 3.5 command parameter for keybinding is not accepting bindings. So a solution is proposed by code in code-behind but how can I pass a parameter to the command from the code?
First, you'll need to add the KeyBinding to your TextBox and set its Command on code-behind. Just add this in the constructor of your View:
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
KeyBinding kb = new KeyBinding();
kb.Command = (DataContext as MyViewModel).SearchCommand;
kb.Key = Key.Enter;
txtSearch.InputBindings.Add(kb);
}
Then, you can bind the Text property of the TextBox named txtSearch to a property of your ViewModel. This way you don't need to pass a parameter as you can use the value of that property in your ViewModel inside the code that executes your Command.
Your ViewModel should look like this:
public class MyViewModel : ObservableObject
{
private string _txtSearch;
public string TxtSearch
{
get { return _txtSearch; }
set
{
if (value != _txtSearch)
{
_txtSearch = value;
OnPropertyChanged("TxtSearch");
}
}
}
private ICommand _searchCommand;
public ICommand SearchCommand
{
get
{
if (_searchCommand == null)
{
_searchCommand = new RelayCommand(p => canSearch(), p => search());
}
return _searchCommand;
}
}
private bool canSearch()
{
//implement canExecute logic.
}
private void search()
{
string text = TxtSearch; //here you'll have the string that represents the text of the TextBox txtSearch
//DoSomething
}
}
If you have access to C# 6 (Visual Studio 2015 and later versions), you can alter the call to the OnPropertyChanged to: OnPropertyChanged(nameof(TxtSearch));. This way you get rid of the "magic string" and eventual renaming of the property won't cause any problem for you.
And then your XAML should look like this: (Notice that you need to specify that te UpdateSourceTrigger must be PropertyChanged, so that your TxtSearch property of your ViewModel stays up to date when you hit the Enter key on your TextBox.
<TextBox Text="{Binding TxtSearch, UpdateSourceTrigger=PropertyChanged}" x:Name="txtSearch"/>
Your ViewModel needs to implement INotifyPropertyChanged and you need a proper ICommand implementation. Here I'll use the RelayCommand.
Those implementations are shown below.
Since your framework is .NET 3.5, implement it like this:
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
This is a implementation of the RelayCommand:
public class RelayCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _execute;
public RelayCommand(Predicate<object> canExecute, Action<object> execute)
{
_canExecute = canExecute;
_execute = execute;
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
WPF is not normally my area, so I am a bit of a newbie, and I am having a bit of trouble figuring out how to achieve something in WPF which was a piece of cake in WinForms. I can't seem to find either the right thread in this forum or the right YouTube tutorial that leads me towards the answer. I am having problems getting a simple DataBinding to a WPF TextBox working correctly. The behaviour that I am trying to achieve is that any changes made to the TextBox are immediately reflected in the source class DataSet. It's a simple display/edit scenario and I'm sure there is a very simple answer.
This is how I would have done it in WinForms....
Form code:
public partial class Form1 : Form
{
private DATARECORD CURRENTUSER;
public Form1()
{
InitializeComponent();
CURRENTUSER = new DATARECORD(#"Data Source=C:\Users\rr187718\Documents\Personal\Programming\DynamicBackup\DynamicBackup\bin\Debug\Data\dbData.sdf");
CURRENTUSER.FncBind(CtlCopiesToKeep, "Value", "tblUser.CopiesToKeep");
}
//Test code to display the value in the DataSet
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show(CURRENTUSER.copiesToKeep.ToString());
}
}
Class code:
public class DATARECORD
{
private string ConnectionString;
private DataSet CurrentRecord;
public int copiesToKeep { get { return Int32.Parse(CurrentRecord.Tables["tblUser"].Rows[0]["CopiesToKeep"].ToString()); } }
public DATARECORD(string connectionString)
{
ConnectionString = connectionString;
CurrentRecord = new DataSet();
SQL SQL = new SQL(2);
DataTable userTable = SQL.fncSelectAsTable(ConnectionString, "tblUser", "USERID=2");
userTable.TableName = "tblUser";
CurrentRecord.Tables.Add(userTable);
userTable.Dispose();
}
public void FncBind(Control c, string type, string field)
{
c.DataBindings.Add(type, CurrentRecord, field, true, DataSourceUpdateMode.OnPropertyChanged);
}
}
I then just have simple TextBox on the main Form called "CtlCopiesToKeep" and a "test" button.
Does anyone know of a nice, simple, example that can show how to do this?
Many thanks in advance,
Dave
EDIT:
Hello Noel. Many thanks for taking the time to explain all that. I have put it altogether, but something seems to be wrong with the binding, because when I change the value in the TextBox it does not update the DataSet. Here is the code and the XAML. If anyone can point me in the right direction then it would be much appreciated.
UPDATED Main code
public partial class MainWindow : Window
{
public DATARECORD SELECTEDUSER;
private string ConnectionString = #"Data Source=C:\Users\rr187718\Documents\Personal\Programming\DynamicBackup\DynamicBackup\bin\Debug\Data\dbData.sdf";
public MainWindow()
{
InitializeComponent();
SELECTEDUSER = new DATARECORD(ConnectionString);
GrdMain.DataContext = SELECTEDUSER;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
SELECTEDUSER.fncShowVals("BasePath");
}
}
UPDATED Class code
public class DATARECORD : INotifyPropertyChanged
{
private string ConnectionString;
private DataSet currentRecord = new DataSet();
private string BasePath = null;
public string basePath
{
get
{
return currentRecord.Tables["tblStorage"].Rows[0]["BasePath"].ToString() ;
}
set
{
BasePath = value;
OnPropertyChanged("BasePath");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public DATARECORD(string connectionString)
{
ConnectionString = connectionString;
SQL SQL = new SQL(ConnectionString, SQLVersion.CE);
DataTable storageTable = SQL.fncSelectAsTable(ConnectionString, "tblStorage", "USERID=2");
storageTable.TableName = "tblStorage";
currentRecord.Tables.Add(storageTable);
storageTable.Dispose();
}
public void fncShowVals(string test)
{
MessageBox.Show(currentRecord.Tables["tblStorage"].Rows[0][test].ToString());
}
protected void OnPropertyChanged(string value)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(value));
}
}
}
XAML for TextBox
<Window x:Class="WpfBind.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid Name="GrdMain">
<TextBox Text="{Binding basePath, Mode=TwoWay, UpdateSourceTrigger =PropertyChanged}" Height="23" HorizontalAlignment="Left" Margin="124,70,0,0" Name="CtlBaseFolder" VerticalAlignment="Top" Width="120" />
<Label Content="BaseFolder" Height="28" HorizontalAlignment="Left" Margin="41,69,0,0" Name="label2" VerticalAlignment="Top" />
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="263,142,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
UPDATE 02/04/2015
I now have this, but I don't understand how it references the DataSet? This code produces a blank textbox and if the value is changed it doesn't update the DataSet:
`private string ___basePath = null;
protected string _basePath
{
get
{
return ___basePath;
}
set
{
___basePath = value;
OnPropertyChanged("basePath");
}
}
public string basePath
{ //<- Bind to this property
get
{
return ___basePath;
}
set
{
_basePath = value;
}
}`
The underlying DataSet value is stored here:
currentRecord.Tables["tblStorage"].Rows[0]["BasePath"].ToString();
Many thanks in advance, Dave.
UPDATE - 02/04/2015 - 2
Hello Noel, I have applied your code, but it's still not working unfortunately (the DataSet does not reflect the changes in the TextBox if I click on the "test" button). Here is the whole code. I massively appreciate your time on this by the way, thanks so much!
public partial class MainWindow : Window
{
private string ConnectionString = #"Data Source=C:\Users\rr187718\Documents\Personal\Programming\DynamicBackup\DynamicBackup\bin\Debug\Data\dbData.sdf";
private readonly DATARECORD _data = null;
public DATARECORD Data
{
get
{
return _data;
}
}
public MainWindow()
{
InitializeComponent();
_data = new DATARECORD(ConnectionString);
DataContext = Data; //All controls connected to this class will now look for their value in 'Data' (DataContext inherits and must be a property because you can only bind to properties)
}
private void button1_Click(object sender, RoutedEventArgs e)
{
Data.fncShowVals("BasePath");
}
}
public class DATARECORD : INotifyPropertyChanged
{
private string ConnectionString;
private DataSet currentRecord = new DataSet();
private string ___basePath = null;
private string _basePath
{
get
{
if (___basePath == null)
{
//We only access the currentRecord if we did not yet stored the value
// otherwise it would read the currentRecord every time you type a char
// in the textbox.
// Also: Pay attention to multiple possible NullReferenceExceptions and IndexOutOfBoundsExceptions
___basePath = currentRecord.Tables["tblStorage"].Rows[0]["BasePath"].ToString();
}
return (___basePath == String.Empty) ? null : ___basePath;
}
set
{
___basePath = (value == null) ? String.Empty : value;
NotifyPropertyChanged("BasePath");
}
}
protected void PushBasePathToDataBase()
{
//Save the value of ___basePath to the database
}
public string BasePath
{ //The Binding recieves/sets the Data from/to this property
get
{
return _basePath;
}
set
{
_basePath = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public DATARECORD(string connectionString)
{
ConnectionString = connectionString;
SQL SQL = new SQL(ConnectionString, SQLVersion.CE);
DataTable storageTable = SQL.fncSelectAsTable(ConnectionString, "tblStorage", "USERID=2");
storageTable.TableName = "tblStorage";
currentRecord.Tables.Add(storageTable);
storageTable.Dispose();
___basePath = currentRecord.Tables["tblStorage"].Rows[0]["BasePath"].ToString();
}
public void fncShowVals(string test)
{
MessageBox.Show(currentRecord.Tables["tblStorage"].Rows[0][test].ToString());
}
protected void NotifyPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
It is great that you are using a binding to seperate data from the visuals. Since that was not really possible in winforms. In order for a binding to work you must do the following:
The textBox must have its DataContext set to the instance of a class which holds the binding-value. DataContext = MyDataInstance; You can set that on the textbox itself or on any parent.
The value as well as the DataContext you want to bind must be a public property. F.e:
private string _name = null;
public string Name{
get{
return _name;
}
set{
_name = value;
NotifyPropertyChanged("Name");
}
}
The Data Class must implement INotifyPropertyChanged
If that is all set up you can write your textbox in xaml:
<TextBlock Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
This Binding binds to the Name property of the instance specified in DataContext. It can retrieve the value from the property and it can write the data to it.
It recieves Data when you call NotifyPropertyChanged("Name"); in your DataClass
It writes Data when the property of the control changes (Requires Mode set to TwoWay and the UpdateSourceTrigger to PropertyChanged)
EDIT (regarding your additional content)
I noticed that you wanted to notify about your private field named "BasePath".
You must notify the property "basePath" and not the field behind it.
That is why I recommend a strict naming convention.
I do name private and protected fields like _privateOrProtected (1 underscore).
I name private or protected fields accessed by bindings properties like ___someData (3 underscores) and the binding property like SomeData. The reason is, that you usually don't want to set the private field directly except from the binding propertie's setter. Setting it directly would not call the NotifyPropertyChanged(); which obviously isn't what you want in almost all cases. And if you keep the 3 underscores throughout your app - everyone familliar with bindings should quickly understand the meaning.
For more complex data you might have a binding property accessing a private/protected property accessing a private field. I would solve it like this: SomeData, _someData, ___someData. You just have to make it clear wich properties or fields can be set in order to update the binding otherwise someone might change the value of ___someData and wonder why the binding isn't updating.
Since this is a quite important point in every WPF app I really want you to understand it. Here is an example for the stuff above:
private bool ___thisIsAwesome = true;
protected bool _thisIsAwesome{
get{
return ___thisIsAwesome;
}
set{
___thisIsAwesome = value;
NotifyPropertyChanged("ThisIsAwesome");
}
}
public bool ThisIsAwesome{ //<- Bind to this property
get{
return ___thisIsAwesome;
}
/*set{
_thisIsAwesome = value;
} NOTE: The setter is not accessable from outside of this class
because nobody can tell me that this is not awesome - it just is.
However I still want to be able to set the property correctly
from within my class (in case I change my mind), that is why I
added the protected property.
If you omit a getter/setter like this one make sure your
<br>Binding Mode</b> does not try to access the omited accessors.
Also check the output window too find possible binding errors
which never throw exceptions.
*/
}
In this code you should now recognize that setting ThisIsAwesome and _thisIsAwesome will both update the binding. But beware of setting ___thisIsAwesome because it won't update the Binding. The setter of ThisIsAwesome is currently not available (whatever reason) and that's why I added the protected property. Do you understand what I want to achieve with that?
EDIT2 (because your code still doesn't work)
public partial class MainWindow : Window {
private readonly MyData _data = null;
public MyData Data{
get{
return _data;
}
}
public MainWindow() {
_data = new MyData();
DataContext = Data; //All controls connected to this class will now look for their value in 'Data' (DataContext inherits and must be a property because you can only bind to properties)
}
}
public class MyData : INotifyPropertyChanged {
private string ___basePath = null;
private string _basePath {
get {
if (___basePath == null) {
//We only access the currentRecord if we did not yet stored the value
// otherwise it would read the currentRecord every time you type a char
// in the textbox.
// Also: Pay attention to multiple possible NullReferenceExceptions and IndexOutOfBoundsExceptions
___basePath = currentRecord.Tables["tblStorage"].Rows[0]["BasePath"].ToString();
}
return (___basePath == String.Empty) ? null : ___basePath;
}
set {
___basePath = (value == null) ? String.Empty : value;
NotifyPropertyChanged("BasePath");
}
}
protected void PushBasePathToDataBase() {
//Save the value of ___basePath to the database
}
public string BasePath{ //The Binding recieves/sets the Data from/to this property
get{
return _basePath;
}
set{
_basePath = value;
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string PropertyName){
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
#endregion INotifyPropertyChanged
}
And finally the textbox in your MainWindow's xaml:
<TextBlock Text="{Binding BasePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
I am just after some information regarding ICommands,
On my WPF application, I have onClick events that adds to a ObservableCollection. So (ObservableCollection.Add()
However, I have 2 similar events to add to a collection also. So I hear I could use the ICommand interface to "execute" for adding/Editing/Removing etc, so I don't need these separate events.
Can someone provide me a example of how I can do this in MVVM. (All adds are in my ViewModel)
Thanks
You might want to look into the "RelayCommand" - it's a common implementation of an ICommand that will simplify your view-model code, allowing you to specify delegates for the ICommand's "Execute" and "CanExecute" methods. You'll find plenty of implementations on the web, but this is the one I use:
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
this._execute = execute;
this._canExecute = canExecute;
}
public virtual bool CanExecute(object parameter)
{
return this._canExecute == null || this._canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public virtual void Execute(object parameter)
{
this._execute(parameter);
}
}
In your VM, expose a command like this:-
public ICommand FooCommand
{
get
{
if (_fooCommand == null)
{
_fooCommand = new RelayCommand(ExecuteFooCommand, CanDoFooCommand);
}
return _fooCommand;
}
}
private void ExecuteFooCommand(object commandParameter)
{
// Code to execute the command.
}
private bool CanDoFooCommand()
{
// Code that indicates whether the command can be executed.
// This will manifest itself in the view by enabling/disabling the button.
}
As the RelayCommand ctr parameters are delegates, you can of course do stuff like this:-
new RelayCommand(o => { // do something }, o => true);
Finally, bind your command to your view button:-
<Button Content="Click me" Command="{Binding FooCommand}" ... />
You can also pass parameters to the command delegate:-
<Button Content="Click me" Command="{Binding FooCommand}" CommandParamter="123" />
(Written out from memory so may not be 100% syntactically correct!)
Going one step further...
To simplify things even more, I use a dynamic property to expose VM commands to the view. In my VM base class I have the following property:-
public dynamic Commands
{
get
{
return _commands;
}
}
Then in a VM's constructor I can create all of its commands like this:-
Commands.FooCommand = new RelayCommand(.....
Commands.BarCommand = ..etc..
In my XAML I bind commands like this:- Command={Binding Commands.FooCommand}.
It's a timesaver as it just means I can hang as many commands off of a single property as I want, rather than expose each one as a separate property as in my earlier example.
What's the right approach to open a child window (for example, to modify a selected item on the main window) keeping MVVM in mind?
Here's what I have: MainWindow.xaml (and in MainWindow.xaml.cs it assigns MainVM as its own DataContext)
I would also like to have: ChildWindow.xaml and barebones ChildWindow.xaml.cs with ChildVM behind controls.
So, now:
How can I popup ChildWindow and pass some object Data to its
ChildVM?
Get the result (true/false) and result data (some complex
object) back to MainVM?
As a bonus, can changes in Data be observed
by MainVM while they are being worked on by ChildVM?
Here's what I tried - it doesn't solve everything, but is this even the right direction?
For (2), I created a subclass of Window, called DialogWindow, which has 3 DependencyProperties: Data (for input data), ResultData (for output data) and ResultValue (for a bool result).
ResultData and ResultValue are both set by the ChildVM of DialogWindow using Binding, and when ResultValue is set, the DialogWindow closes.
At the moment, the ChildWindow is launched (for all intents and purposes) from MainWindow.xaml.cs - kinda bad. I can then pass some input data, like so:
ChildDialogWindow w = new ChildDialogWindow();
w.Data = myDataObj;
So, now I need to have a property Data on ChildVM, and set in ChildDialogWindow.xaml.cs. Again, making .xaml.cs thicker.
I thought that maybe a better approach that avoids MainWindow.xaml.cs would be some kind of DialogService which is passed to MainVM as a dependency. But then, how can I pass values to the ChildVM?
Try this.
Make a DialogService.cs
public class DialogService
{
public void Show(FrameworkElement view, ChildViewModel ChildVM)
{
Window window = new Window();
window.Content = view;
window.DataContext = ChildVM;
// For closing this dialog using MVVM
ChildVM.RequestClose += delegate
{
window.Close();
};
window.Show();
}
}
Now in ChildVm class, add this
public ICommand CloseCommand
{
get
{
if (_closeCommand == null)
_closeCommand = new RelayCommand(param => this.OnRequestClose());
return _closeCommand;
}
}
public event EventHandler RequestClose;
void OnRequestClose()
{
EventHandler handler = this.RequestClose;
if (handler != null)
handler(this, EventArgs.Empty);
}
Now, this the way to launch this
public void OpenChildDailog()
{
DialogService service = new DialogService();
ChildViewModel childVM = new ChildViewModel();
childVM.Data = ; // Assign whatever you want
childVM.ResultData = ;
service.Show(new ChildView(), childVM);
// Now get the values when the child dailog get closed
var retVal = childVM.ResultValue;
}
I'm using the ICommand helper "RelayCommand," and pushing an IntPtr datatype to the new ViewModel (or use any other object.) Lots of cookie cutter stuff.
Main View:
<Button Command="{Binding DataContext.ShowObjectInfoCommand}" CommandParameter="{Binding ObjectOffset}" Content="{Binding Name}"/>
MainViewModel:
private RelayCommand _showObjectInfoCommand;
public RelayCommand ShowObjectInfoCommand { get { return _showObjectInfoCommand ?? (_showObjectInfoCommand = new RelayCommand(ExeShowObjectInfoCommand)); } set { } } //Draw Specific Item Table
void ExeShowObjectInfoCommand(object parameter)
{
ViewObjectInfo objInfo = new ViewObjectInfo();
IObjectOffsetParameter viewModel = objInfo.DataContext as IObjectOffsetParameter;
viewModel.ObjectOffset = (IntPtr)parameter;
objInfo.Show();
}
New ViewModel + interface:
interface IObjectOffsetParameter
{
IntPtr ObjectOffset { get; set; }
}
class ViewModelObjectInfo : ViewModelBase, IObjectOffsetParameter
{
public ViewModelObjectInfo()
{
}
private IntPtr _objectOffset; //Entity Offset
public IntPtr ObjectOffset
{
get { return _objectOffset; }
set { if (_objectOffset != value) { _objectOffset = value; RaisePropertyChanged("Offset"); } }
}
}
New View code-behind:
InitializeComponent();
ViewModelObjectInfo viewModel = new ViewModelObjectInfo();
this.DataContext = viewModel;
New View xaml:
<TextBlock Text="{Binding ObjectOffset}"/>