I'm developing my first application in WPF with the pattern MVC and I have a question.
I have created a usercontrol from type Grid to made a custom title bar. This grid contains a X button and I want to associate this button to a command.
My grid in XAML:
<Grid x:Class="Views.TitleBarView"
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"
Style="{DynamicResource TitleStyleGrid}"
x:Name="barView">
<Label x:Name="labelAppName" Style="{DynamicResource TitleStyleLabel}" Content="Title"/>
<Button x:Name="bttnClose" Style="{DynamicResource ButtonStyleCloseWindow}" Command="{Binding CloseCommand}"/>
<Button x:Name="buttonMinimize" Style="{DynamicResource ButtonStyleMinimizeWindow}" Command="{Binding MinimizeCommand}"/>
</Grid>
The C# view:
public partial class TitleBarView : Grid
{
public TitleBarView()
{
InitializeComponent();
TitleBarViewModel tvm = new TitleBarViewModel();
tvm.RequestClose += (s, e) => this.Close();
tvm.RequestMinimize+= (s, e) => this.Minimize();
DataContext = tvm;
}
private void Close()
{
Window.GetWindow(this).Close();
}
private void Minimize()
{
Application.Current.MainWindow.WindowState = System.Windows.WindowState.Minimized;
}
}
The C# viewModel:
public class TitleBarViewModel : ViewModelBase, IRequestMinimizeViewModel, IRequestCloseViewModel
{
private RelayCommand minimizeCommand;
protected RelayCommand closeCommand;
public event EventHandler RequestMinimize;
public event EventHandler RequestClose;
#region MinimizeCommand
public ICommand MinimizeCommand
{
get
{
if (minimizeCommand == null)
{
minimizeCommand = new RelayCommand(Minimize);
}
return minimizeCommand;
}
}
private void Minimize()
{
RequestMinimize(this, null);
}
#endregion
#region CloseCommand
public ICommand CloseCommand
{
get
{
if (closeCommand == null)
{
closeCommand = new RelayCommand(Close);
}
return closeCommand;
}
}
protected void Close()
{
RequestClose(this, null);
}
#endregion
}
I saw that it's not recommended to set a DataContext on a userControl. And when I do this, I can't change the close command. For example I want that when the main windows calls command close it calls Application.Current.Shutdown(); instead of Application.Current.Shutdown();
I know that I have something wrong but I'm too confuse to solve it. Can you explain me how to create command for userControl ? (Or just tell me what I'm doing wrong)
Thank you
Related
I have a ViewModel, from which I show a Window that should fade out. It fades only first time, and then It stops.
public class MessageBoxViewModel
{
private MessageBoxView _message;
private MessageBoxResult _result = MessageBoxResult.No;
public MessageBoxViewModel()
{
//...creating commands...
}
private void Window_Closing(object sender, CancelEventArgs e)
{
//Close window with fade out animation
_message.Closing -= Window_Closing;
e.Cancel = true;
var animation = new DoubleAnimation
{
To=0,
Duration=TimeSpan.FromSeconds(1),
FillBehavior = FillBehavior.Stop
};
animation.Completed += (s, a) => { _message.Close(); };
_message.BeginAnimation(UIElement.OpacityProperty, animation);
}
public MessageBoxResult Show(string Text, string Title)
{
//...setting properties which View is bound to
_message = new MessageBoxView
{
DataContext = this
};
_message.Closing += Window_Closing;
_message.ShowDialog();
return _result;
}
}
And this is how I call messagebox in different ViewModel:
class SomeViewMNodel : INotifyPropertyChanged
{
private MessageBoxViewModel _message = new MessageBoxViewModel();
public SomeViewModel()
{
//....
}
private void ShowMessages(object parameter) //Command on click of some button
{
_message.Show("Hey I'm fading.", "Fade out"); //Fading is succesfully done
_message.Show("Hey I'm fading second time.", "Fade out"); //Fading doesn't work anymore
}
}
I have tried to stop an animation as suggested here, but that doesn't seem to work. Neither does Opacity property actually change - a simple check with var j = _message.GetAnimationBaseValue(UIElement.OpacityProperty) ==> allways shows value of 1, in animation.Completed or after inicializing new Window.
I've figured that animation works If I don't use _message variable, but instead declare a new instance of ViewModel, e.g. var win = new MessageBoxViewModel(). But I'm using this custom MessageBox for all errors & notifications in many ViewModels, so I would like to use only _message variable If possible (I would make it global).
Without MVVM and re-initiliazing instance of Window I can make animation working everytime, but how can I animate Window in MVVM properly?
I don't see the point in making the Window instance global and reuse the Window instance. You should generally avoid creating global (static) instances.
Creating a small Window instance from time to time is quite affordable. Instantiation costs are unnoticeable in this case.
Anyway, if you want to reuse a Window instance, you are not allowed to close it. Closing a Window disposes its unmanaged resources.
If you want to use Window.Close(), you have to override the virtual Window.OnClosing method (or listen to the Window.Closing event) and cancel closing and instead set the window's visibility to Visibilty.Collapsed:
private void Window_Closing(object sender, CancelEventArgs e)
{
//Close window with fade out animation
_message.Closing -= Window_Closing;
e.Cancel = true;
var animation = new DoubleAnimation
{
To=0,
Duration=TimeSpan.FromSeconds(1),
FillBehavior = FillBehavior.Stop
};
animation.Completed += (s, a) => _message.Visibility = Visibility.Collapsed; ;
_message.BeginAnimation(UIElement.OpacityProperty, animation);
}
But as some have noticed already, this implementation is violating the MVVM pattern. You are not allowed to introduce a coupling between the view and the view model. The goal of MVVM is to remove this exact coupling.
The view model is not allowed to handle any UI components.
The following example shows how implement a dialog infrastructure that complies with the MVVM pattern.
The example consists of four simple steps
Create a DialogViewModel
Create the Dialog, which will extend Window. It will use a DataTemplate to show its content based on the DialogViewModel (or subtypes) in the Dialog.DataContext
Create a public property e.g., DialogViewModel in your view model that needs to show the dialog.
Let the parent control e.g. MainWindow actually show the Dialog.
Example implementation
Implement a view model that serves as the data source for the actual dialog window:
DialogViewModel.cs
This view model defines an AcceptCommand and a CancelCommand which can be bound to corresponding dialog buttons.
When one of the commands is executed a CloseRequested event is raised.
The constructor takes a delegate which serves as a callback that is invoked when the dialog was closed.
public class DialogViewModel : INotifyPropertyChanged
{
public DialogViewModel(string dialogCaption, string message, Action<DialogViewModel> dialogClosedHandler)
{
this.DialogCaption = dialogCaption;
this.Message = message;
this.DialogClosedHandler = dialogClosedHandler;
}
public void HandleResult() => this.DialogClosedHandler.Invoke(this);
private string dialogCaption;
public string DialogCaption
{
get => this.dialogCaption;
set
{
this.dialogCaption = value;
OnPropertyChanged();
}
}
private string message;
public string Message
{
get => this.message;
set
{
this.message = value;
OnPropertyChanged();
}
}
public ICommand AcceptCommand => new RelayCommand(param => this.IsAccepted = true);
public ICommand CancelCommand => new RelayCommand(param => this.IsAccepted = false);
private bool isAccepted;
public bool IsAccepted
{
get => this.isAccepted;
set
{
this.isAccepted = value;
OnPropertyChanged();
OnCloseRequested();
}
}
public event EventHandler<DialogEventArgs> CloseRequested;
public event PropertyChangedEventHandler PropertyChanged;
private Action<DialogViewModel> DialogClosedHandler { get; }
protected virtual void OnCloseRequested()
{
this.CloseRequested?.Invoke(this, new DialogEventArgs(this));
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Implement the dialog window, which will show its content based on the DialogViewModel using a DataTemplate. To create different types of specialized dialogs, simply ceate a specialized dialog view model and a corresponding DataTemplate.
Every eventual dialog animation is also implemented in this class using XAML and EventTrigger, which triggers on a DialogClosed routed event.
The Dialog will listen to the DialogViewModel.CloseRequested event to close itself. Since you wished to reuse the Dialog instance, the Dialog intercepts the invocation of Close() to collapse itself. This behavior can be enabled using the constructor.
After closing itself, the Dialog sets the DialogEventArgs.Handled property to true, which will trigger the invocation of the dialog closed callback (which was registered with the DialogViewModel), so that the calling view model, that showed the dialog, can continue to execute:
Dialog.xaml.cs
public partial class Dialog : Window
{
#region DialogClosedRoutedEvent
public static readonly RoutedEvent DialogClosedRoutedEvent = EventManager.RegisterRoutedEvent(
"DialogClosed",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(Dialog));
public event RoutedEventHandler DialogClosed
{
add => AddHandler(Dialog.DialogClosedRoutedEvent, value);
remove => RemoveHandler(Dialog.DialogClosedRoutedEvent, value);
}
#endregion
private bool IsReuseEnabled { get; }
public Dialog(bool isReuseEnabled = false)
{
InitializeComponent();
this.IsReuseEnabled = isReuseEnabled;
this.DataContextChanged += OnDialogViewModelChanged;
}
public Dialog(DialogViewModel dialogViewModel) : this()
{
this.DataContext = dialogViewModel;
}
private void OnDialogViewModelChanged(object sender, DependencyPropertyChangedEventArgs e)
{
HandleDialogNewViewModel(e.OldValue as DialogViewModel, e.NewValue as DialogViewModel);
}
private void HandleDialogNewViewModel(DialogViewModel oldDialogViewModel, DialogViewModel newDialogViewModel)
{
if (oldDialogViewModel != null)
{
oldDialogViewModel.CloseRequested -= CloseDialog;
}
if (newDialogViewModel != null)
{
newDialogViewModel.CloseRequested += CloseDialog;
}
}
private void CloseDialog(object sender, DialogEventArgs e)
{
Close();
e.Handled = true;
}
#region Overrides of Window
protected override void OnClosing(CancelEventArgs e)
{
if (!this.IsReuseEnabled)
{
return;
}
e.Cancel = true;
Dispatcher.InvokeAsync(
() => RaiseEvent(new RoutedEventArgs(Dialog.DialogClosedRoutedEvent, this)),
DispatcherPriority.Background);
base.OnClosing(e);
}
#endregion
private void DialogClosedAnimation_OnCompleted(object? sender, EventArgs e)
{
this.Visibility = Visibility.Collapsed;
}
}
Dialog.xaml
To customize the layout, edit the DataTemplate:
<Window x:Class="Dialog"
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"
Height="450"
Width="800"
Title="{Binding DialogCaption}">
<Window.Resources>
<!--
To create more specialized dialogs,
create a dedicated DataTemplate for each dialog view model type.
-->
<DataTemplate DataType="{x:Type local:DialogViewModel}">
<StackPanel>
<TextBlock Text="{Binding Message}"/>
<StackPanel Orientation="Horizontal">
<Button Content="Ok" Command="{Binding AcceptCommand}" />
<Button Content="Cancel" IsDefault="True" IsCancel="True" Command="{Binding CancelCommand}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</Window.Resources>
<!-- Animations triggered by the DialogClosed event -->
<Window.Triggers>
<EventTrigger RoutedEvent="local:Dialog.DialogClosed">
<BeginStoryboard>
<Storyboard Completed="DialogClosedAnimation_OnCompleted">
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:1" FillBehavior="Stop"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" FillBehavior="Stop">
<DiscreteObjectKeyFrame KeyTime="0:0:1" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
<Grid>
<ContentPresenter Content="{Binding}"/>
</Grid>
</Window>
To trigger the displaying of the Dialog let the view model create the DialogViewModel and assign it to a public property.
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public void SaveToFile(object data, string filePath)
{
// Check if file exists (pseudo)
if (string.IsNullOrWhiteSpace(filePath))
{
// Show the dialog
this.DialogViewModel = new DialogViewModel("File Exists Dialog", "File exists. Replace file?", OnDialogResultAvailable);
}
else
{
Save(data, filePath);
}
}
public void Save(object data, string filePath)
{
// Write data to file
}
private void OnDialogResultAvailable(DialogViewModel dialogViewModel)
{
if (dialogViewModel.IsAccepted)
{
// User has accepted to overwrite file
Save(data, filePath);
}
}
private DialogViewModel dialogViewModel;
public DialogViewModel DialogViewModel
{
get => this.dialogViewModel;
set
{
this.dialogViewModel = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
To actually show the dialog, let the parent Window (e.g., MainWindow) listen to the property changes of ViewModel.DialogViewModel e.g., by setting up a Binding:
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public static readonly DependencyProperty CurrentDialogViewModelProperty = DependencyProperty.Register(
"CurrentDialogViewModel",
typeof(DialogViewModel),
typeof(MainWindow),
new PropertyMetadata(default(DialogViewModel), MainWindow.OnDialogViewModelChanged));
public DialogViewModel CurrentDialogViewModel
{
get => (DialogViewModel) GetValue(MainWindow.CurrentDialogViewModelProperty);
set => SetValue(MainWindow.CurrentDialogViewModelProperty, value);
}
private static void OnDialogViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
{
return;
}
(d as MainWindow).ShowDialog(e.NewValue as DialogViewModel);
}
private void ShowDialog(DialogViewModel dialogViewModel)
{
this.Dialog.DataContext = dialogViewModel;
this.Dialog.ShowDialog();
// Alternative recommended approach:
// var dialog = new Dialog(dialogViewModel);
// dialog.ShowDialog();
}
private Dialog Dialog { get; set; }
public MainWindow()
{
InitializeComponent();
// Create a reusable dial instance (not recommended)
this.Dialog = new Dialog(true);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
(this.DataContext as ViewModel).SaveToFile(null, string.Empty);
}
}
MainWindow.xaml
<Window x:Class="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:"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<Style TargetType="local:MainWindow">
<-- Observe view model DialogViewModel property using data binding -->
<Setter Property="CurrentDialogViewModel" Value="{Binding DialogViewModel}" />
</Style>
</Window.Resources>
<Button Content="Show Dialog" Click="Button_Click" />
</Window>
Remarks
You can improve reusability by moving the code implemented in MainWindow to an attached behavior.
I have a ListBox that is in my UserControl which is displayed on my main window. My ViewModel for the main window contains two ICommands, remove and add from listbox. I have successfully bounded my remove command to the listbox from the main window. However, I have a separate window that opens from the main window which includes a button to add a ListBoxItem to the listbox. My main window ViewModel does not exist in the context of the second window. I can not instantiate another ViewModel as it will not contain the items that already exist in my ObservableCollection found in the ViewModel.
How can I go about solving this in an MVVM way? Should I Create another ViewModel for the second window View? If so, how can I ensure all my pre-existing ListBoxItems are visible to it?
Update: As requested, minimal code.
My UserControl containing my ListBox:
<UserControl>
<Grid>
<StackPanel>
<ListBox x:Name="lstBox" ItemsSource="{Binding myList}" DisplayMemberPath="Desc">
</ListBox>
<Button x:Name="btn_Remove" Content="Remove" Command="{Binding RemoveCommand}"
CommandParameter="{Binding ElementName=lstBox, Path=SelectedIndex}"/>
</StackPanel>
</Grid>
</UserControl>
My MainWindow ViewModel containing the ICommands:
public class MainWindowViewModel {
private RelayCommand removeCommand;
private RelayCommand addCommand;
public ObservableCollection<myObjModel> MyList {
get;
set;
}
public void createList() {
ObservableCollection<myObjModel> myList = new ObservableCollection<myObjModel>();
myList.Add(new myObjModel { Desc = "test" });
MyList = myList;
}
public ICommand RemoveCommand {
get {
if (removeCommand == null) {
removeCommand = new RelayCommand(param => remove((int)param));
}
return removeCommand;
}
}
public void remove(int i) {
MyList.RemoveAt(i);
}
public ICommand AddCommand {
get {
if (addCommand == null) {
addCommand = new RelayCommand(param => add((string)param));
}
return addCommand;
}
}
public void add(string desc) {
Telegrams.Add(new TelegramyObjModelmModel { Desc = desc });
}
}
My MainWindow XAML:
<Window
xmlns:uc="clr-namespace:myClass">
<Grid>
<uc:UserControl x:Name="myUserControl" Loaded="myUserControl_Loaded"/>
</Grid>
</Window>
Followed by the code behind
public partial class MainWindow : Window {
MainWindowViewModel ViewModelObject;
public MainWindow() {
InitializeComponent();
}
private void myUsereControl_Loaded(object sender, RoutedEventArgs e) {
ViewModelObject = new MainWindowViewModel();
ViewModelObject.createList();
myUsereControl_Loaded.DataContext = ViewModelObject;
}
}
I am very new to WPF and relatively new to C# programming (programming in general), and I'm trying to develop a WPF application.
I have tried to go through several posts similar to this, but I can't seem to find the answer of why this is not working.
So, I'm having a hard time understanding the MVVM architecture, how and what it requires to switch between multiple user controls binded to a single <ContentControl />.
From what I understand and read so far, is that I have to bind the view model like this:
<ContentControl Content="{Binding ApplicationViewModel}"/>
So here is what I want to a achieve:
An ApplicationWindow.xaml with sidebar menu on the left side that will be shown at all times when the application is running, and a <ContentControl/> on the remaining space. Buttons shown on the sidebar menu will be:
Main (will show MainView.xaml User Control, should be the default User Control)
Settings (will show SettingsView.xaml User Control)
Exit (will close the application)
I understand that I need to bind the buttons to ICommand commands, and I understand the concept of a RelayCommand.cs class.
So let's jump into the simplified code of my idea and figure out what I need to understand and what I may have misunderstood in the process.
What MainView.xaml and SettingsView.xaml contain are not important right now, as I'm just trying to figure out how to show them in my application.
Here's the ApplicationWindow.xaml:
<Window x:Class="WpfApp1.ApplicationWindow"
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:WpfApp1"
xmlns:v="clr-namespace:WpfApp1.View"
xmlns:vm="clr-namespace:WpfApp1.ViewModel"
mc:Ignorable="d"
Title="ApplicationWindow" Height="1080" Width="1920"
WindowStyle="None" WindowState="Maximized">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:MainViewModel}">
<v:MainView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsViewModel}">
<v:SettingsView/>
</DataTemplate>
</Window.Resources>
<DockPanel>
<!--Menu bar on the left-->
<Border DockPanel.Dock="Left">
<StackPanel Orientation="Vertical" Background="Gray" Width="120">
<Button Content="Main" Command="{Binding ShowMainCommand}"/>
<Button Content="Settings" Command="{Binding ShowSettingsCommand}"/>
<Button Content="Exit" Command="{Binding ExitApplicationCommand}"/>
</StackPanel>
</Border>
<!--The content control that view the current view-->
<ContentControl Content="{Binding ApplicationViewModel}"/>
</DockPanel>
</Window>
Note: DataContext is set to ApplicationViewModel.cs in App.xaml.cs by overriding the OnStartup() method.
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ApplicationWindow app = new ApplicationWindow
{
DataContext = new ApplicationViewModel()
};
app.Show();
}
}
Here's the ApplicationViewModel.cs:
public class ApplicationViewModel : ViewModelBase
{
#region Fields
private List<ViewModelBase> _viewModels;
private ViewModelBase _currentViewModel;
private ICommand _showMainCommand;
private ICommand _showSettingsCommand;
private ICommand _exitApplicationCommmand;
#endregion
#region Constructor
public ApplicationViewModel()
{
ViewModels = new List<ViewModelBase>
{
new MainViewModel(),
new SettingsViewModel()
};
CurrentViewModel = ViewModels[0];
}
#endregion
#region Public Properties
public List<ViewModelBase> ViewModels
{
get
{
return _viewModels;
}
set
{
if (_viewModels != value)
{
_viewModels = value;
OnPropertyChanged(nameof(ViewModels));
}
}
}
public ViewModelBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
if(_currentViewModel != value)
{
_currentViewModel = value;
OnPropertyChanged(nameof(CurrentViewModel));
}
}
}
#endregion
#region Commands
public ICommand ShowMainCommand
{
get
{
if(_showMainCommand == null)
{
_showMainCommand = new RelayCommand(action => ShowMain());
}
return _showMainCommand;
}
}
public ICommand ShowSettingsCommand
{
get
{
if (_showSettingsCommand == null)
{
_showSettingsCommand = new RelayCommand(action => ShowSettings());
}
return _showSettingsCommand;
}
}
public ICommand ExitApplicationCommand
{
get
{
if (_exitApplicationCommmand == null)
{
_exitApplicationCommmand = new RelayCommand(action => ExitApplication());
}
return _exitApplicationCommmand;
}
}
#endregion
#region Private Methods
private void ShowMain()
{
CurrentViewModel = ViewModels[0];
}
private void ShowSettings()
{
CurrentViewModel = ViewModels[1];
}
private void ExitApplication()
{
MessageBoxResult result = MessageBox.Show("Are you sure you want to exit?", "Exit", MessageBoxButton.YesNo);
if (result == MessageBoxResult.Yes)
{
System.Windows.Application.Current.Shutdown();
}
}
#endregion
}
So, from what I understand, the ApplicationWindow.xaml should be able to determine which view to show out from what the CurrentViewModel is set to.
For the sake of information (or miss-information), here are ViewModelBase.cs:
public class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And RelayCommand.cs:
public class RelayCommand : ICommand
{
#region Fields
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
#endregion
#region Constructors
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public RelayCommand(Action<object> execute) : this(execute, null)
{
}
#endregion
#region ICommand
public bool CanExecute(object parameters)
{
return _canExecute == null ? true : _canExecute(parameters);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameters)
{
_execute(parameters);
}
#endregion
}
I hope my thought process on this was clear to you, and that one of you smart programmers out there can help solving this, and help me understand why this isn't turning out as I want it to.
In case of what I'm trying to do is harder than Elon Musk's project on making life multiplanetary, feel free to explain why and suggest me a better way to
Your Content control binding should be pointed at the actual property you change when switching ViewModels
<ContentControl Content="{Binding CurrentViewModel}"/>
I'm trying to call a function in the viewmodel on startup the MVVM way. I thought I had it correct, but the code is never hitting the function call.
Here's my xaml:
<Window x:Class="TestWin.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:l="clr-namespace:Timeserver"
xmlns:viewmodel="clr-namespace:Timeserver.ViewModels"
Title="MainWindow"
Width="893"
Height="Auto"
Background="LightGray"
ResizeMode="NoResize"
SizeToContent="Height">
<Window.DataContext>
<viewmodel:MainWindowViewModel />
</Window.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadData}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Here's my viewmodel (the necessary parts):
namespace TestWin.ViewModels
{
class MainWindowViewModel
{
private StructsModel model; // my model class
private ICommand loadDataCmd;
private ICommand showTimeWindowCmd;
private ICommand toggleExecuteCommand { get; set; }
private bool canExecute = true;
public bool CanExecute
{
get
{
return this.canExecute;
}
set
{
if (this.canExecute == value)
{
return;
}
this.canExecute = value;
}
}
public ICommand ToggleExecuteCommand
{
get
{
return toggleExecuteCommand;
}
set
{
toggleExecuteCommand = value;
}
}
public ICommand ShowTimeWindowCmd
{
//code here
}
public ICommand LoadDataCmd
{
get
{
return loadDataCmd;
}
set
{
loadDataCmd = value;
}
}
public void LoadData(object parameter)
{
model.GetData();
}
public MainWindowViewModel()
{
this.model = new StructsModel();
LoadDataCmd = new RelayCommand(LoadData, param => this.canExecute);
ShowTimeWindowCmd = new RelayCommand(ShowTimeWindow, param => this.canExecute);
toggleExecuteCommand = new RelayCommand(ChangeCanExecute);
}
public void ShowTimeWindow(object parameter)
{
//code here
}
public void ChangeCanExecute(object obj)
{
canExecute = !canExecute;
}
}
}
The function in question that is not being hit is LoadData(). It calls GetData() in my model class, which I cannot show for reasons. Not sure what else to try. I've seen other questions on SO doing the same thing I'm doing. My other function ShowTimeWindow is set up the exact same way and does get hit.
If you really want the call to be made at Loaded, you could remove the xaml code
<Window.DataContext>
<viewmodel:MainWindowViewModel />
</Window.DataContext>
and bind the viewmode from code behind, like Mat said above. Then, the Command you have bound will be triggered (if it has the same name as the one in the viewmodel), and you will not need to call the vm.LoadData() in the constructor.
Also, if the command is not used for anything else than to load data at "Loaded", CanExecute is useless.
You can create your ViewModel in code behind. Than you can call the methods you need. If you want to go for excellence you can use a Factory pattern or a dependency injection container (e.g. Windsor)
public MainWindow()
{
InitializeComponent();
MainWindowViewModel vm = new MainWindowViewModel();
DataContext = vm;
vm.LoadData();
}
I am trying to implement Help functionality for my wpf application which is following the MVVM pattern. I have my help file present, which contains many pages according to the application. Now I need to integrate this into my application.
Here are my requirements:
Pressing F1 opens a certain page in the help file depending on the view model. For this, I guess, I need to bind the F1 command to my view model. How do we bind keys in views?
Pressing F1 on a text field opens help for that text field. I think it will be the same as requirement 1. But the problem here is how will I know that a certain text field, button, or radio button is selected?
Listen for the key in the view (or a base class of the view) and call execute on a HelpCommand on the DataContext.
Pass the control that has focus (or its id, or tag, ...) as an argument to the HelpCommand.
Alternative way to find the focussed control by using the FocusManager
Here is an example:
ContextHelp C#:
public static class ContextHelp
{
public static readonly DependencyProperty KeywordProperty =
DependencyProperty.RegisterAttached(
"Keyword",
typeof(string),
typeof(ContextHelp));
public static void SetKeyword(UIElement target, string value)
{
target.SetValue(KeywordProperty, value);
}
public static string GetKeyword(UIElement target)
{
return (string)target.GetValue(KeywordProperty);
}
}
ViewBase:
public abstract class ViewBase : UserControl
{
public ViewBase()
{
this.KeyUp += ViewBase_KeyUp;
this.GotFocus += ViewBase_GotFocus;
}
void ViewBase_GotFocus(object sender, RoutedEventArgs e)
{
FocusManager.SetIsFocusScope(this, true);
}
void ViewBase_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.F1)
{
var viewModel = this.DataContext as ViewModelBase;
if (viewModel != null)
{
var helpTopic = "Index";
var focusedElement =
FocusManager.GetFocusedElement(this) as FrameworkElement;
if (focusedElement != null)
{
var keyword = ContextHelp.GetKeyword(focusedElement);
if (!String.IsNullOrWhiteSpace(keyword))
{
helpTopic = keyword;
}
}
viewModel.HelpCommand.Execute(helpTopic);
}
}
}
}
ViewModelBase:
public abstract class ViewModelBase: INotifyPropertyChanged
{
public ICommand HelpCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName="")
{
var p = PropertyChanged;
if (p != null)
{
p(this, new PropertyChangedEventArgs(propertyName));
}
}
}
AViewModel:
class AViewModel : ViewModelBase
{
public AViewModel()
{
HelpCommand = new RelayCommand(HelpCommandExecuted, (p)=>true);
}
private void HelpCommandExecuted(object parameter)
{
var topic = parameter as string;
if (!String.IsNullOrWhiteSpace(topic))
{
HelpText = String.Format("Information on the interesting topic: {0}.", topic);
}
}
private string _helpText;
public string HelpText
{
get { return _helpText; }
private set
{
if (_helpText != value)
{
_helpText = value;
OnPropertyChanged();
}
}
}
}
AView C#:
public partial class AView : ViewBase
{
public AView()
{
InitializeComponent();
}
}
AView XAML:
<local:ViewBase x:Class="WpfApplication2.AView"
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:WpfApplication2"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Label Content="{Binding HelpText}" Margin="10,254,10,0" VerticalAlignment="Top" Height="36"/>
<Button local:ContextHelp.Keyword="Button Info" Content="Button" HorizontalAlignment="Left" Margin="192,32,0,0" VerticalAlignment="Top" Width="75"/>
<TextBox local:ContextHelp.Keyword="TextBox Info" HorizontalAlignment="Left" Height="23" Margin="29,32,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
<CheckBox local:ContextHelp.Keyword="CheckBox Info" Content="CheckBox" HorizontalAlignment="Left" Margin="29,80,0,0" VerticalAlignment="Top"/>
<ComboBox local:ContextHelp.Keyword="ComboBox Info" HorizontalAlignment="Left" Margin="138,80,0,0" VerticalAlignment="Top" Width="120"/>
</Grid>
</local:ViewBase>
MainWindow XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2" x:Class="WpfApplication2.MainWindow"
Title="MainWindow" Height="700" Width="500">
<Grid x:Name="ViewPlaceholder">
</Grid>
</Window>
MainWindow C#:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var view = new AView();
var viewModel = new AViewModel();
view.DataContext = viewModel;
ViewPlaceholder.Children.Clear();
ViewPlaceholder.Children.Add(view);
}
}