I would like to use ICommand to change the Paddle1.Y int value of my ViewModel. Am I supposed to create a class implementing ICommand interface? I have done that. But since it is a class, it doesn't have access to my ViewModel's Paddle1 property without creating a property for it. I would prefer to create the command within my ViewModel for this reason. At this point I'm trying to pass the Paddle1 to the Command as a CommandParameter in XAML. I am failing at this, and I'm not sure it is the cleanest approach to editing the state of my ViewModel either.
Could I get a code example of my UpKeyPressed command being bound to either a button or the keyboard up key? With no CommandParameter would be more clean, if the command could access my ViewModel Paddle1 property.
My ViewModel:
namespace Pong.Core.ViewModels
{
public class GamePlayViewModel
{
private readonly Paddle Paddle1;
private Paddle Paddle2;
public GamePlayViewModel()
{
Paddle1 = new Paddle();
Paddle2 = new Paddle();
UpKeyPressed();
}
public ICommand UpKeyPressed()
{
var r = new UpKeyPressed();
r.Execute(Paddle1);
return r;
}
}
public class UpKeyPressed : ICommand
{
public void Execute(object parameter)
{
var paddle = parameter as Paddle;
Debug.Assert(paddle != null, "paddle != null");
paddle.IncreaseY();
Debug.WriteLine(paddle.Y);
}
public bool CanExecute(object parameter)
{
return parameter != null;
}
public event EventHandler CanExecuteChanged;
}
}
My XAML page that uses the viewmodel as a dataContext:
<Window x:Class="Pong.Windows.Views.GamePlayView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Pong.Core.ViewModels;assembly=Pong.Core"
Title="GamePlayView" Height="350" Width="525">
<Grid>
<Button CommandParameter="{Binding ElementName=Paddle1}"
Command="{StaticResource UpKeyPressed}" >
Click
</Button>
</Grid>
<Window.DataContext>
<local:GamePlayViewModel/>
</Window.DataContext>
<Window.InputBindings>
<KeyBinding Command="{Binding Path=UpKeyPressed}"
Key="O"
Modifiers="Control"/>
</Window.InputBindings>
</Window>
Data structure of my solution
My attempt to fix:
namespace Pong.Core.ViewModels
{
public class GamePlayViewModel
{
private readonly Paddle Paddle1;
private Paddle Paddle2;
private ICommand _doSomething;
public ICommand DoSomethingCommand
{
get
{
if (_doSomething == null)
{
_doSomething = new UpKeyPressed(Paddle1);
}
return _doSomething;
}
}
public GamePlayViewModel()
{
Paddle1 = new Paddle();
Paddle2 = new Paddle();
}
}
public class UpKeyPressed : ICommand
{
private Paddle Paddle1;
public UpKeyPressed(Paddle paddle)
{
Paddle1 = paddle;
}
public void Execute(object parameter)
{
//var paddle = parameter as Paddle;
//Debug.Assert(paddle != null, "paddle != null");
//paddle.IncreaseY();
Paddle1.IncreaseY();
//Debug.WriteLine(paddle.Y);
Debug.WriteLine(Paddle1.Y);
}
public bool CanExecute(object parameter)
{
return Paddle1 != null;
}
public event EventHandler CanExecuteChanged;
}
}
XAML attempt (no errors but not workling upon pressing the 'O' key):
<Window x:Class="Pong.Windows.Views.GamePlayView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels="clr-namespace:Pong.Core.ViewModels;assembly=Pong.Core"
Title="GamePlayView" Height="350" Width="525">
<Grid>
</Grid>
<Window.DataContext>
<viewModels:GamePlayViewModel/>
</Window.DataContext>
<Window.InputBindings>
<KeyBinding Command="{Binding DoSomethingCommand}"
Key="O"
Modifiers="Control"/>
</Window.InputBindings>
Looked at your attempt, there are some things we need to be fix, first your CanExecute should not involve the parameter anymore:
public bool CanExecute(object parameter) {
return Paddle1 != null;
}
Secondly your XAML binding is wrong, you already have DataContext of your view-model flown in your visual tree, you just need a simple Binding with some Path specified like this:
<KeyBinding Command="{Binding DoSomethingCommand}"
Key="O"
Modifiers="Control"/>
Related
I have been attempting to implement a Sudoku game within a WPF application I am making. I found the following site that pretty much gave me the perfect starting point to try and add the sudoku to my app.
There is however a major difference which I didn't think much of at first. The code from this site bases everything off a single Window, no UserControls at all, which in itself isn't an issue. However, my current implementation bases the whole content of the app on a ContentControl element.
To skip useless details, here is my MainWindow.xaml file (with everything unrelated to the issue removed):
<Window x:Class="BasicGameApp.MainWindow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BasicGameApp.MainWindow"
xmlns:viewModel="clr-namespace:BasicGameApp.MainWindow.MVVM.ViewModel"
mc:Ignorable="d"
Height="700"
Width="1080"
WindowStartupLocation="CenterScreen"
WindowStyle="None"
ResizeMode="NoResize"
Background="Transparent"
AllowsTransparency="True">
<Window.DataContext>
<viewModel:MainViewModel/>
</Window.DataContext>
<Border>
<Grid>
<ContentControl Grid.Row="1"
Grid.Column="1"
Margin="10"
Content="{Binding CurrentView}"/>
</Grid>
</Border>
</Window>
I based my UI on this YouTube tutorial if anyone is curious.
The MainViewModel.cs looks like this:
namespace BasicGameApp.MainWindow.MVVM.ViewModel
{
class MainViewModel : ObservableObject
{
#region Commands
public RelayCommand HomeViewCommand { get; set; }
public RelayCommand SudokuViewCommand { get; set; }
#endregion
#region ViewModels
public HomeViewModel HomeVM { get; set; }
public SudokuViewModel SudokuVM { get; set; }
#endregion
private object _currentView;
public object CurrentView
{
get => _currentView;
set
{
_currentView = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
HomeVM = new HomeViewModel();
SudokuVM = SudokuViewModel.GetInstance(new SudokuView());
HomeViewCommand = new RelayCommand(o =>
{
CurrentView = HomeVM;
});
SudokuViewCommand = new RelayCommand(o =>
{
CurrentView = SudokuVM;
});
}
}
}
And here are the ObservableObject RelayCommand classes:
namespace BasicGameApp.MainWindow.Core
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class RelayCommand : ICommand
{
private Action<object> _execute;
private Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
}
As you can see, everything is programmed correctly and works as intended EXCEPT, the SudokuViewModel. As you can see from the code, the SudokuViewModel isn't instantiated as a public class (this is from the tutorial I stated at the beginning). From that tutorial, the instantiation of the view and associated viewmodel is done as follows in the App.xaml.cs:
public partial class App : Application
{
public void ApplicationStartup(object sender, StartupEventArgs args)
{
MainWindow mainWindow = new MainWindow(); // Instantiate the main window
mainWindow.ViewModel = ViewModelClass.GetInstance(mainWindow); // Get an instance of the ViewModel and set the View's ViewModel pointer
mainWindow.Show(); // Now display the view
}
}
My App.xaml file isn't empty however and contains the following:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/BasicGameApp.MainWindow;component/Themes/Generic.xaml"/>
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type viewModel:HomeViewModel}">
<view:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:SudokuViewModel}">
<view:SudokuView/>
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
Needless to say that the difference between these two setups makes things rather difficult. I attempted to change the DataTemplate to set the ViewModel property on the SudokuView but I can't seem to get it to Bind correctly:
<view:SudokuView ViewmModel="{Binding //The calling ViewModel}"/>
I'm currently out of ideas as to how I can either adapt the sudoku code to work "without" a viewmodel or adapt the DataTemplate to provide the view with the SudokuViewModel.
Please bare in mind that I am initially an Android developper and this is simply to learn new skills on a personal level.
This question already has an answer here:
WPF MenuItem style parameters not available on menu first open
(1 answer)
Closed 2 years ago.
Each ListBox item has a ContextMenu "Kick" option. It does trigger the CanKickPlayer() method when I right-click the first ListBox item for the first time, but never again if I repeat the process on the same or a different ListBox item. Questions:
How to do so CanKickPlayer() triggers every time I choose the ContextMenu "Kick" option?
Why is the passed parameter in CanKickPlayer() method null?
MainWindow.xaml
<Window x:Class="ContextMenuTriggeredOnce.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="200">
<GroupBox Header="Players">
<ListBox ItemsSource="{Binding Players}" SelectedItem="{Binding SelectedPlayer}">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Kick" Command="{Binding KickPlayerCommand}" CommandParameter="{Binding SelectedPlayer}" />
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
</GroupBox>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainViewModel MainViewModel { get; set; }
public MainWindow()
{
MainViewModel = new MainViewModel();
DataContext = MainViewModel;
InitializeComponent();
}
}
MainViewModel.cs
public class MainViewModel : BaseViewModel
{
public ObservableCollection<string> Players { get; set; } = new ObservableCollection<string>();
private string _selectedPlayer;
public string SelectedPlayer
{
get => _selectedPlayer;
set
{
if (value == null)
{
return;
}
SetProperty(ref _selectedPlayer, value);
}
}
private readonly RelayCommand _kickPlayerCommand;
public ICommand KickPlayerCommand => _kickPlayerCommand;
public MainViewModel()
{
Players.Add("Player1");
Players.Add("Player2");
_kickPlayerCommand = new RelayCommand(OnKickPlayer, CanKickPlayer);
}
private void OnKickPlayer(object command)
{
Players.Remove(command.ToString());
_kickPlayerCommand.InvokeCanExecuteChanged();
}
private bool CanKickPlayer(object command)
{
return command != null;
}
}
BaseViewModel.cs
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, newValue))
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
return false;
}
}
RelayCommand.cs
class RelayCommand : ICommand
{
private readonly Action<object> _executeAction;
private readonly Func<object, bool> _canExecuteAction;
public event EventHandler CanExecuteChanged;
public RelayCommand(Action<object> executeAction, Func<object, bool> canExecuteAction)
{
_executeAction = executeAction;
_canExecuteAction = canExecuteAction;
}
public void Execute(object parameter) => _executeAction(parameter);
public bool CanExecute(object parameter) => _canExecuteAction?.Invoke(parameter) ?? true;
public void InvokeCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
You need to set the CommandParameter before Command.
<MenuItem Header="Kick" CommandParameter="{Binding SelectedPlayer}" Command="{Binding KickPlayerCommand}" />
(I don't use the Command pattern. I prefer using the Click event and from the Click event handler calling a function in the ViewModel. If I need to disable the menu item, I use a bool property in the ViewModel and bind that to the IsEnabled property on the menu item.)
I am trying to learn WPF/MVVM and for educational reason I create a simple application. I have some issues trying to implement a Command Object.
When a button control is clicked I want the background color of the Grid change to yellow using a Command Object. There are a lot of stuff about how to do this, but I want to do it with the clean way. Generally I want to achieve a loose coupling between View, ViewModel and the Command Object in order to test those classes.
Also i do not want to use some Libraries like Prism because I have the need to fully understand MVVM first.
I have a code sample but of course it does not have functionality. Just represented it for convenience reason.
My view XAML
<Window x:Class="Calendar.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Calendar"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="480">
<Grid Background="{Binding BackgroundColour}" Margin="0,0,2,0">
<Button Margin="197,247,200,-239" Grid.Row="3" Grid.ColumnSpan="2" Command="{Binding SubmitCommand}">Color</Button>
</Grid>
My ModelView class
public class MainWindowViewModel : INotifyPropertyChanged {
//Command part
ICommand SubmitCommand;
public MainWindowViewModel(ICommand command) {
SubmitCommand = command;
}
//Data Binding part
public event PropertyChangedEventHandler PropertyChanged;
private Brush backgroundColour = (Brush)new BrushConverter().ConvertFromString("Red");
public Brush BackgroundColour {
get { return this.backgroundColour; }
set {
if (value != this.backgroundColour) {
this.backgroundColour = value;
var handler = this.PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs("BackgroundColour"));
}
}
}
(it also has a data binding part but it does not have to do with my issue)
You would like not to have anything related to windows like colors(Brushes or Brush) in the viewmodel. Refer my below code.
<Window x:Class="MVVMNav_Learning.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MVVMNav_Learning"
mc:Ignorable="d"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<local:ColorConverterConverter x:Key="ColorConverterConverter"></local:ColorConverterConverter>
</Window.Resources>
<Grid>
<Grid Background="{Binding BackgroundColour,Converter={StaticResource ColorConverterConverter}}" Margin="0,0,2,0">
<Button Margin="50" Command="{Binding SubmitCommand}">Color</Button>
</Grid>
</Grid>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
public class ViewModel:INotifyPropertyChanged
{
private MyColor backColor;
public MyColor BackgroundColour
{
get { return backColor; }
set { backColor = value; OnPropertyChanged("BackgroundColour"); }
}
public ICommand SubmitCommand { get; set; }
public ViewModel()
{
BackgroundColour = MyColor.Red;
SubmitCommand = new BaseCommand(Execute);
}
public void Execute(object parameter)
{
BackgroundColour = MyColor.Yellow;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
public enum MyColor
{
Red,
Green,
Yellow
}
public class BaseCommand : ICommand
{
private Action<object> _method;
public event EventHandler CanExecuteChanged;
public BaseCommand(Action<object> method)
{
_method = method;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_method.Invoke(parameter);
}
}
public class ColorConverterConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
MyColor color = (MyColor)value;
switch (color)
{
case MyColor.Red:
return Brushes.Red;
case MyColor.Green:
return Brushes.Green;
case MyColor.Yellow:
return Brushes.Yellow;
default:
{
return Brushes.Red;
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You need to create a public Property for the ICommand SubmitCommand and you can use a private DelegateCommand in its getter/setter.
You are not very clearly stating your question, but I gamble it to be: How to configure the command parameter for the viewmodel's constructor to have it change the backgroundcolour?
Commands do their work by having them implement ICommand.Execute(Object) So basically you want to have the command object you pass to the constructor to have a method like:
void Execute(object parameter)
{
viewModel.BackGroundColor=Brushes.Yellow;
}
This is awkward: the command is passed from outside the viewmodel, but it must have a reference to it to change its back colour. You may want to rethink your design.
Moreover: for the databinding engine to see the SubmitChangedCommand it must be a property:
public ICommand SubmitChangesCommand {get;set;}
So I have a View with two subviews. One of the subviews is an on screen keyboard with textbox. Below that are some buttons which are part of a different subview. See below:
When I press the keyboard buttons it types in the textbox. Both the subview with the buttons and the subview with the keyboard have their own ViewModels. My question is, how do I reference the keyboard view from the button view (so I can get the contents of the text field, for example, or clear it if the user clicks "Go Back").
I'm trying to conceptualize it, but I can't figure out how I would get the same instance of the ViewModel of the keyboard that the Main View has.
I can create a variable:
private KeyboardViewModel keyboard;
But how do I instantiate that variable with the instance that the Main View already has (so I can access those properties from the button viewmodel)?
The main problem is that you misplaced your datasource in one of your ViewModel when the datasource is actually needed to be reuse in multiple View/ViewModel. What you need to do is to refactor the datasource out into a singleton instance or an seperate instance that can be injected into different ViewModels' constructor. By decoupling out the datasource from a particular ViewModel can give it freedom for different place to access.
public class DataCache
{
private static DataCache singletonInstance;
// You can have freedom to choose the event-driven model here
// Using traditional Event, EventAggregator, ReactiveX, etc
public EventHandler OnMessageChanged;
private DataCache()
{
}
public static DataCache Instance
{
get { return singletonInstance ?? (singletonInstance = new DataCache()); }
}
public string OnScreenMessage { get; set; }
public void AddStringToMessage(string c)
{
if (string.IsNullOrWhiteSpace(c)) return;
OnScreenMessage += c;
RaiseOnMessageChanged();
}
public void ClearMessage()
{
OnScreenMessage = string.Empty;
RaiseOnMessageChanged();
}
private void RaiseOnMessageChanged()
{
if (OnMessageChanged != null)
OnMessageChanged(null, null);
}
}
public class MainViewModel : ViewModelBase
{
private readonly MessageViewModel messageVM;
private readonly KeyboardViewModel keyboardVM;
private readonly ButtonsViewModel buttonsVM;
private readonly DataCache dataCache;
public MainViewModel()
{
messageVM = new MessageViewModel();
keyboardVM = new KeyboardViewModel();
buttonsVM = new ButtonsViewModel();
}
public ViewModelBase MessageViewModel { get { return messageVM; } }
public ViewModelBase KeyboardViewModel { get { return keyboardVM; } }
public ViewModelBase ButtonsViewModel { get { return buttonsVM; } }
}
public class MessageViewModel : ViewModelBase
{
private readonly DataCache dataCache = DataCache.Instance;
public MessageViewModel()
{
dataCache.OnMessageChanged += RaiseMessageChanged;
}
private void RaiseMessageChanged(object sender, EventArgs e)
{
OnPropertyChanged("Message");
}
public string Message
{
get { return dataCache.OnScreenMessage; }
set { dataCache.OnScreenMessage = value; }
}
}
public class KeyboardViewModel : ViewModelBase
{
private readonly DataCache dataCache = DataCache.Instance;
private ICommand onClickButtonCommand;
public ICommand OnClickButton
{
get
{
return onClickButtonCommand ?? (onClickButtonCommand = new RelayCommand(p => dataCache.AddStringToMessage((string)p)));
}
}
}
public class ButtonsViewModel : ViewModelBase
{
private readonly DataCache dataCache = DataCache.Instance;
private ICommand onGoBackCommand;
public ICommand OnGoBackButton
{
get
{
return onGoBackCommand ?? (onGoBackCommand = new RelayCommand(p => dataCache.ClearMessage()));
}
}
}
public class RelayCommand : ICommand
{
#region Fields
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
#endregion Fields
#region Constructors
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
if (handler != null) handler(this, EventArgs.Empty);
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion ICommand Members
}
<Window x:Class="StudentScoreWpfProj.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StudentScoreWpfProj"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:MainViewModel,IsDesignTimeCreatable=True}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<local:MessgaeView DataContext="{Binding MessageViewModel}" />
<local:KeyboardView Grid.Row="1" DataContext="{Binding KeyboardViewModel}" />
<local:ButtonsView Grid.Row="2" DataContext="{Binding ButtonsViewModel}" />
</Grid>
<UserControl x:Class="StudentScoreWpfProj.ButtonsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:StudentScoreWpfProj"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:ButtonsViewModel,IsDesignTimeCreatable=True}"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel Orientation="Horizontal">
<Button Content="GoBack" Command="{Binding OnGoBackButton}"></Button>
<Button Content="Continue"></Button>
</StackPanel>
</Grid>
<UserControl x:Class="StudentScoreWpfProj.KeyboardView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:StudentScoreWpfProj"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:KeyboardViewModel,IsDesignTimeCreatable=True}"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel Orientation="Horizontal">
<Button Content="A" Command="{Binding OnClickButton}" CommandParameter="A"></Button>
<Button Content="B" Command="{Binding OnClickButton}" CommandParameter="B"></Button>
<Button Content="C" Command="{Binding OnClickButton}" CommandParameter="C"></Button>
</StackPanel>
</Grid>
<UserControl x:Class="StudentScoreWpfProj.MessgaeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:StudentScoreWpfProj"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:MessageViewModel,IsDesignTimeCreatable=True}"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox Text="{Binding Message}"/>
</Grid>
You could do several things ...
You could create a static instance for easy access, and expose what you want on it (not recommended, read comments).
You could use dependency injection, so your other viewmodel will take the keyboard viewmodel as a parameter (please have a look at my other answer, it'll get you started quicly).
You could use a messenger to help you talk between them as well. most mvvm frameworks will have some ( have a look at this SO question, and at this code project article to get you started. They are specifically for MVVM light, but they'll help you understand the concept) .
How about using ServiceLocator from Microsoft.Practices.ServiceLocation?
ServiceLocator.Current.GetInstance<ViewModelName>();
I am setting the DataContext for my View in the View's Constructor to an instance of my ViewModel, just standard stuff. Shortly thereafter, an UPDATE_RECENT_DOCUMENTS_LIST Event fires from the Event Aggregator which my ViewModel catches correctly. A property is changed and the onPropertyChanged method is called, but it fails as the PropertyChanged event is null.
The very next thing I do is an action to the UI which raises a CREATE_PROJECT Event and the same ViewModel is receiving events, except now, the PropertyChanged event is no longer null and everything works as expected.
Is there a specific amount of time that has to pass after setting the DataContext before it registers to the PropertyChanged Event? Is there an event I can wait for that ensures the PropertyChanged event is not null?
Also, I did not run into this problem using standard .NET events, just after integrating Prism and using the very convenient EventAggregator.
I am showing my code behind of the View and the ViewModel, omitting the View XAML for brevity.
ToolBarView.xaml.cs:
namespace ToolBarModule
{
public partial class ToolBarView : UserControl
{
public ToolBarView(ToolBarViewModel toolBarViewModel)
{
InitializeComponent();
this.DataContext = toolBarViewModel;
}
}
}
ToolBarViewModel.cs
namespace ToolBarModule
{
public class ToolBarViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ToolBarCommands baseCommands;
private IEventAggregator eventAggregator;
private KickStartEvent kickStartEvent;
private SubscriptionToken subscriptionToken;
private ObservableCollection<IDocumentReference> recentDocuments = new ObservableCollection<IDocumentReference>();
private ActionCommand newTest;
private ActionCommand openTest;
private ActionCommand saveTest;
private ActionCommand exitApplication;
public ToolBarViewModel(){}
public ToolBarViewModel(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
baseCommands = new ToolBarCommands(eventAggregator);
kickStartEvent = eventAggregator.GetEvent<KickStartEvent>();
subscriptionToken = kickStartEvent.Subscribe(kickStartEventHandler, ThreadOption.UIThread, true, toolBarEventHandlerFilter);
}
public ICommand NewTest
{
get
{
if (newTest == null)
{
newTest = new ActionCommand(baseCommands.NewTestAction);
}
return newTest;
}
}
public ICommand OpenTest
{
get
{
if (openTest == null)
{
openTest = new ActionCommand(baseCommands.OpenTestAction);
}
return openTest;
}
}
public ICommand SaveTest
{
get
{
if (saveTest == null)
{
saveTest = new ActionCommand(baseCommands.SaveTestAction);
}
return saveTest;
}
}
public ICommand ExitApplication
{
get
{
if (exitApplication == null)
{
exitApplication = new ActionCommand(baseCommands.ExitApplicationAction);
}
return exitApplication;
}
}
public ObservableCollection<IDocumentReference> RecentDocuments
{
get
{
return recentDocuments;
}
set
{
recentDocuments = value;
onPropertyChanged("RecentDocuments");
}
}
private void onPropertyChanged(string propertyChanged)
{
if (PropertyChanged != null)
{
PropertyChanged(this,new PropertyChangedEventArgs(propertyChanged));
}
}
private void kickStartEventHandler(KickStartEventsArgs e)
{
switch (e.EventType)
{
case KickStartEventsArgs.KickStartEventType.CREATE_PROJECT:
onPropertyChanged("RecentDocuments");
break;
case KickStartEventsArgs.KickStartEventType.UPDATE_RECENT_DOCUMENTS_LIST:
RecentDocuments.Clear();
foreach (IDocumentReference recentDocs in e.KickStartTestList)
{
RecentDocuments.Add(recentDocs);
}
onPropertyChanged("RecentDocuments");
break;
}
}
}
}
You can also try to set the DataContext of a Grid or an Element below the UserControl. For me it worked.
Example (Doesn't work if you use DependencyProperty):
Code Behind:
public MyUserControl()
{
InitializeComponent();
this.DataContext = new { LabelText = "Hello World!" };
}
XAML
<UserControl x:Class="CoolProject.ViewModel.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Label x:Name="myLabel" Content="{Binding LabelText}"/>
Example 2 (My working code):
Code Behind:
public MyUserControl()
{
InitializeComponent();
this.myGrid.DataContext = new { LabelText = "Hello World!" };
}
XAML
<UserControl x:Class="CoolProject.ViewModel.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid x:Name="myGrid">
<Label x:Name="myLabel" Content="{Binding LabelText}"/>
</Grid>
You have to name your UserControl in XAML and use it in binding. Something like following code:
<UserControl x:Name="uc" >
.
.
.
<TextBox Text="{Binding UserName, Mode=TwoWay, ElementName=uc}"/>
Where uc is a name of your UserControl, and Also try to set DataContext when UserControl loaded.
Hope this help.