Is there a way to create "Instance only" ICommand implementation for Custom Control, WITHOUT static classes "behind"?
I am trying to update previously created custom control.
One of the goals is to ensure multiinstance capability.
If two or more instances of the same custom control was used in the same application, there is (as expected) interferences from any static clases that are used behind.
I figured out how to get rid of the most, but having troubles with ICommand.
Given GUI items on the Custom Control have Command that must be only valid within the User Control instance - instead of this now the Command is interfering with all instances (as example CanExecute makes GUI items active on UserControl Instances where the "local" conditions are not met).
You can create your command and expose it as a property of your ViewModel, then bind to it in your control:
In your ViewModel:
public ICommand MyCommand {get;set;} // construct your command and set it here
in your control:
<Button Command="{Binding MyCommand}"/>
if you are not using MVVM pattern, then you should create the same field in your DataContext (probably in your controls code behind)
you can also use Dependency properties in order to define your command, if you change your command after your user control is created, you should use it.
In general:
In order to know your options when writing in WPF / C# I recommend reading about MVVM pattern, dependency properties, DataContext and Binding - you may know some of this already.
I think you might be confused by the fact that the CanExecute and Execute methods do not have a parameter linking them to the object upon which they are supposed to act.
But remember that the ICommand interface must be implemented by a class, and objects of that class can and should have fields, typically initialized in the constructor.
For example, if you follow the MVVM pattern (as already mentioned by Ron.B.I.), the command typically has a reference to the viewmodel. Or you can use something like a RelayCommand, and capture the viewmodel in a delegate or lambda closure object.
Thank you very much for the answers and clarifications!
You gave me the all deciding kick so I figured it out. I added my examples complete on purpouse.
Following your advices (Ron B I & Dennis) I first wanted read more about the ViewModel.
Under http://msdn.microsoft.com/en-ca/magazine/dd419663.aspx there are examples with non-static class behind. So the solution was simply to add new class in my user control (Exactly as shown on the mentioned site - Figure 3 - some name changed - Copyright belongs to Josh Smith joshsmithonwpf.wordpress.com ):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
namespace WpfCommandControl
{
class CommandImplementation : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public CommandImplementation(Action<object> execute)
: this(execute, null)
{
}
public CommandImplementation(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
}
Then in the User Control "Window"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace WpfCommandControl
{
public partial class CommandControl : UserControl, INotifyPropertyChanged
{
#region [ Private Members ]
private bool _canActivated = false;
private int _counter = 0;
CommandImplementation _activateCommand;
#endregion
#region [ Properties ]
public int CommandCounter
{
get
{
return _counter;
}
set
{
_counter = value;
OnNotifyPropertyChanged("CommandCounter");
}
}
public bool CanActivated
{
get
{
return _canActivated;
}
set
{
_canActivated = value;
OnNotifyPropertyChanged("CanActivated");
}
}
#endregion
#region [ Property_Changed_Utilities ]
public event PropertyChangedEventHandler PropertyChanged;
private void OnNotifyPropertyChanged(String info)
{
// Note: Do not forget to add interface "INotifyPropertyChanged" to your class.
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
# region [ Commands ]
public ICommand ActivateCommand
{
get
{
return _activateCommand;
}
}
#endregion
#region [ Constructor ]
public CommandControl()
{
InitializeComponent();
_activateCommand = new CommandImplementation(param => this.Activate(), param => this.CanActivated);
}
#endregion
#region [ Methods ]
void Activate()
{
CommandCounter++;
}
#endregion
}
}
Most important part:
Command is implemented as Property:
public ICommand ActivateCommand
{
get
{
return _activateCommand;
}
}
So it makes sure it will return the actual instance related Command, that was instantiated with Lambda-Expression in the User Control's constructor:
public CommandControl()
{
InitializeComponent();
_activateCommand = new CommandImplementation(param => this.Activate(), param => this.CanActivated);
}
The Lambda - Expression makes the connection to the logic begind:
param => this.Activate()
For the Activate() function wich will be executed as Command is fired
void Activate()
{
CommandCounter++;
}
And
param => this.CanActivated
For passing the local logic behind for the ICommand CanExecute Property, thus giving you control on when the command can be executed.
In my case I used property that can be bind to the CheckBox, but you can also do it another way...
public bool CanActivated
{
get
{
return _canActivated;
}
set
{
_canActivated = value;
OnNotifyPropertyChanged("CanActivated");
}
}
Again it is as shown from Josh Smith joshsmithonwpf.wordpress.com - I just changed it to instantiate in the constructor instead of check if the private member is null and delivering new instance if needed in the GET part of the Command Property.
Rest of the code is just implementation of needed Properties and OnNotifyPropertyChanged as shown on MSDN.
XAML is simple - just for the proof of concept.
<UserControl x:Class="WpfCommandControl.CommandControl"
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:local="clr-namespace:WpfCommandControl"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid>
<StackPanel>
<CheckBox Content="Activate" IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=CanActivated}" />
<Button Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl},
Path=ActivateCommand}"
Content="Click me"
IsEnabled="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl},
Path=CanActivated}" />
<Label Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=CommandCounter}" IsEnabled="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=CanActivated}" />
</StackPanel>
</Grid>
As you can see there is only a CheckBox - Binding will provide Enable/Disable of Button.
Click on Button fires the Command that simply increment counter - shown on the Label again trough binding.
Putting all together:
Just one simple XAML Form with four User Controls:
<Window x:Class="CommandsTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CommandsTest"
xmlns:uctrl="clr-namespace:WpfCommandControl;assembly=WpfCommandControl"
Title="MainWindow"
Width="525"
Height="350">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<uctrl:CommandControl Grid.Row="0" Grid.Column="0" />
<uctrl:CommandControl Grid.Row="0" Grid.Column="1" />
<uctrl:CommandControl Grid.Row="1" Grid.Column="0" />
<uctrl:CommandControl Grid.Row="1" Grid.Column="1" />
</Grid>
Firing the commands on every control is exactly as needed inside the element.
All is solved in the WPF way - using Commands and Bindings without any direct interaction with GUI elements, thus the GUI can be exchanged without need of updates in the code behind.
Once again thank you for showing me that there is also another (instance safe) way to implement custom commands in WPF.
Related
I come from a WPF background so I thought I'd experiment with building a to-do app in WinUI 3. The app structure is a little overdesigned as I'm trying build it out like a more complex app. For that reason I have a ToDoTaskView and ToDoTaskViewModel, along with a MainWindowView and MainWindowViewModel even though it'd be much easier to build the entire app in a single XAML file.
The ToDoTaskView has a delete button, but the delete command lives on the MainWindowViewModel, as that's where the list that it must be deleted from lives. I think this a pretty common pattern where a sub-view needs to send a command to a parent view model.
The (abridged) MainWindowView:
<UserControl>
<ItemsRepeater ItemsSource="{Binding Tasks}">
<DataTemplate>
<local:ToDoTaskView />
</DataTemplate>
</ItemsRepeater>
</UserControl>
And the (heavily abridged) ToDoTaskView:
<UserControl>
<Button Command="???">Delete</Button>
</UserControl>
In WPF there's many ways to deal with this.
RoutedCommand
My prefered method. The MainWindowView can listen for a custom ToDoTaskDeleted routed command and bind to the command on the view model. Then any UI element anywhere underneath MainWindowView can fire said event and rest easy knowing it'll be handled somewhere above it on the visual tree.
There's no RoutedCommand in WinUI 3, and even worse, routed events are locked down and you can't define custom ones. So even building a custom RoutedCommand implementation would be difficult.
DynamicResource
I can define a StandardUICommand in MainWindowView.Resources, bind it to the command in the view model, then in ToDoTaskView I can use {DynamicResource DeleteCommand} to have the resource system search up the visual tree for the command.
Except I can't. WinUI3 doesn't have DynamicResource, only StaticResource. And since the two views are in different XAML files, and ToDoTaskView in a templated context, StaticResource can't resolve the resource name between them.
I think this could work for resources in App.xaml, but I'd rather not shove every command into the top level scope instead of keeping them where they belong.
All the commanding examples in the Microsoft docs seem to assume that the button and handler are in the same file, or they directly pass a reference to the command through to the child view's DataContext.
RelativeAncestor
Peter below reminded me that I tried this too, and found it's missing in WinUI 3. RelativeSource doesn't support any kind of ancestor discovery.
Manual Kludge
Setting up a direct reference from ToDoTaskViewModel to MainWindowViewModel is certainly possible, but I hate it. After all, who's to guarantee that this particular to do item is part of a list at any one moment? Maybe it lives in a pop-up dialog as a reminder? Handling this kind of thing through the visual tree is the Correct(tm) way to do it.
I wouldn't accept a PR from a coworker on my WPF project with this solution. But I can't seem to find any better way in WinUI 3.
Have I missed something about WinUI 3? Is it just not mature enough yet to have a solution? It seems like this scenario isn't so uncommon that it would be completely unsupported.
In this case, I'd create an ICommand dependency property, DeleteCommand and and bind a command in the view model. Here's a sample code using the CommunityToolkit.Mvvm NuGet package.
MainWindow.xaml
The MainWindow is named, "ThisWindow" in this case, so we can access its ViewModel from the ItemTemplate.
The DeleteCommandParameter is bound to the DataContext of the item, ToDoTaskViewModel in this case.
<Window
x:Class="ToDoApp.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:local="using:ToDoApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="ThisWindow"
mc:Ignorable="d">
<Grid RowDefinitions="Auto,*">
<StackPanel
Grid.Row="0"
Orientation="Horizontal">
<TextBox x:Name="NewToDo" />
<Button
Command="{x:Bind ViewModel.AddToDoCommand}"
CommandParameter="{x:Bind NewToDo.Text, Mode=OneWay}"
Content="Add" />
</StackPanel>
<ScrollViewer Grid.Row="1">
<ItemsRepeater ItemsSource="{x:Bind ViewModel.ToDoTasks, Mode=OneWay}">
<ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="local:ToDoTaskViewModel">
<local:ToDoTaskView
DeleteCommand="{Binding ElementName=ThisWindow, Path=ViewModel.DeleteToDoCommand}"
DeleteCommandParameter="{x:Bind}"
ToDo="{x:Bind ToDo, Mode=OneWay}" />
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer>
</Grid>
</Window>
MainWindow.xaml.cs
using Microsoft.UI.Xaml;
namespace ToDoApp;
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
public MainWindowViewModel ViewModel { get; } = new();
}
MainWindowViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
namespace ToDoApp;
[ObservableObject]
public partial class MainWindowViewModel
{
[ObservableProperty]
private ObservableCollection<ToDoTaskViewModel> toDoTasks = new();
[RelayCommand]
private void AddToDo(string todo)
{
ToDoTasks.Add(new ToDoTaskViewModel() { ToDo = todo });
}
[RelayCommand]
private void DeleteToDo(ToDoTaskViewModel toDoTask)
{
ToDoTasks.Remove(toDoTask);
}
}
ToDoTaskView.xaml
<UserControl
x:Class="ToDoApp.ToDoTaskView"
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:local="using:ToDoApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid ColumnDefinitions="*,Auto">
<TextBlock
Grid.Column="0"
Text="{x:Bind ToDo, Mode=OneWay}" />
<Button
Grid.Column="1"
Command="{x:Bind DeleteCommand, Mode=OneWay}"
CommandParameter="{x:Bind DeleteCommandParameter, Mode=OneWay}"
Content="Delete" />
</Grid>
</UserControl>
ToDoTaskView.xaml.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Windows.Input;
namespace ToDoApp;
public sealed partial class ToDoTaskView : UserControl
{
public static readonly DependencyProperty ToDoProperty = DependencyProperty.Register(
nameof(ToDo),
typeof(string),
typeof(ToDoTaskView),
new PropertyMetadata(default));
public static readonly DependencyProperty DeleteCommandProperty = DependencyProperty.Register(
nameof(DeleteCommand),
typeof(ICommand),
typeof(ToDoTaskView),
new PropertyMetadata(default));
public static readonly DependencyProperty DeleteCommandParameterProperty = DependencyProperty.Register(
nameof(DeleteCommandParameter),
typeof(object),
typeof(ToDoTaskView),
new PropertyMetadata(default));
public ToDoTaskView()
{
this.InitializeComponent();
}
public string ToDo
{
get => (string)GetValue(ToDoProperty);
set => SetValue(ToDoProperty, value);
}
public ICommand DeleteCommand
{
get => (ICommand)GetValue(DeleteCommandProperty);
set => SetValue(DeleteCommandProperty, value);
}
public object DeleteCommandParameter
{
get => (object)GetValue(DeleteCommandParameterProperty);
set => SetValue(DeleteCommandParameterProperty, value);
}
}
ToDoTaskViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
namespace ToDoApp;
[ObservableObject]
public partial class ToDoTaskViewModel
{
[ObservableProperty]
private string toDo = string.Empty;
}
Ok I have a solution. I cannot emphasize enough how much of a disgusting hack this is. Normally I'd be embarrassed to post this, but the only ones who should be embarrassed are Microsoft for publishing Win UI 3 in its current state and claiming it's capable of making real applications.
The gist of this is to mimic Ancestor-type RelativeSource binding in WPF. We create two attached properties - ParentContextViewType to specify the type of the ancestor we're looking for - and ParentContextView which is automatically assigned a reference to the desired parent view instance when the child loads. (I'd have made ParentContextView a readonly property, but of course, Win UI doesn't support that...) Then for the child button, we do a RelativeSource Self binding to the attached ParentContextView property, then adding the rest of the path, just like we would with a legit ancestor type bind.
Here goes (and may god have mercy on my soul):
using System;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
namespace ParentBinding
{
public static class Hacks
{
public static DependencyProperty ParentContextView =
DependencyProperty.RegisterAttached(
"ParentContextView",
typeof(FrameworkElement),
typeof(Hacks),
new PropertyMetadata(null));
public static FrameworkElement GetParentContextView(DependencyObject d)
{
return d.GetValue(ParentContextView) as FrameworkElement;
}
public static void SetParentContextView(DependencyObject d, FrameworkElement view)
{
d.SetValue(ParentContextView, view);
}
public static DependencyProperty ParentContextViewTypeProperty =
DependencyProperty.RegisterAttached(
"ParentContextViewType",
typeof(Type),
typeof(Hacks),
new PropertyMetadata(null, (d, e) =>
{
if (!(d is FrameworkElement fe))
return;
if (e.OldValue != null)
fe.Loaded -= OnParentContextFeLoaded;
if (e.NewValue != null)
fe.Loaded += OnParentContextFeLoaded;
}));
private static void OnParentContextFeLoaded(object sender, RoutedEventArgs e)
{
if (!(sender is FrameworkElement fe))
return;
var type = GetParentContextViewType(fe);
if (type == null)
return;
while (!type.IsAssignableFrom(fe.GetType()) &&
(fe = VisualTreeHelper.GetParent(fe) as FrameworkElement) != null)
{
}
SetParentContextView(sender as DependencyObject, fe);
}
public static Type GetParentContextViewType(DependencyObject d)
{
return d.GetValue(ParentContextViewTypeProperty) as Type;
}
public static void SetParentContextViewType(DependencyObject d, Type val)
{
d.SetValue(ParentContextViewTypeProperty, val);
}
}
}
A use-case:
Model stuff:
using Microsoft.UI.Xaml.Input;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace ParentBinding
{
public class Command : ICommand
{
Action<object> _action;
public Command(Action<object> action)
{
_action = action;
}
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter) => true;
public void Execute(object? parameter)
{
_action?.Invoke(parameter);
}
}
public class Parent
{
public ObservableCollection<Child> Children { get; set; }
private Command _deleteChildCommand;
public ICommand DeleteChildCommand =>
_deleteChildCommand ?? (_deleteChildCommand = new Command((p) =>
{
if (!(p is Child ch))
return;
this.Children.Remove(ch);
}));
}
public class Child
{
public string Name { get; set; }
public override string ToString() => this.Name;
}
}
Main Window:
<Window x:Class="ParentBinding.MainWindow"
x:Name="_main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ParentBinding"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<ListView DataContext="{Binding ElementName=_main, Path=Parent}"
ItemsSource="{Binding Children}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Child">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" />
<Button local:Hacks.ParentContextViewType="ListView"
Grid.Column="1"
CommandParameter="{Binding}"
Content="Delete"
Command="{Binding
Path=(local:Hacks.ParentContextView).DataContext.DeleteChildCommand,
RelativeSource={RelativeSource Self}}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Window>
using Microsoft.UI.Xaml;
namespace ParentBinding
{
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
public Parent Parent { get; } = new Parent
{
Children = new System.Collections.ObjectModel.ObservableCollection<Child>
{
new Child
{
Name = "Larry"
},
new Child
{
Name = "Curly"
},
new Child
{
Name = "Moe"
}
}
};
}
}
Amazingly, it works, and one of the reasons I was so curious to try it and post it is that it is, more or less, a general purpose substitute for ancestor type binding in WinUI 3. Hope someone finds it useful.
I know there are questions similar to this one, but nothing seems to help make things click. I know in MVVM there is a Model, ViewModel, and View. I will try to make this simple so I can understand what components go inside which. In this particular code I want to be able to control the visibility of the side bar menu with the top bar menu. Then I will create a navigation for the side bar menu, but trying to figure out what goes where seems very hard and I am not sure why.
FYI: If you are trying to recreate the namespaces were changed and I am using Material Design NuGet Package
This should be the Base Model known as the BindableBase (I Think?) The Model contains the INotifyPropertyChanged, but because this is a base I shouldn't have any variables in this or should I?
Model:
using System.ComponentModel;
using System.Threading.Tasks;
namespace MainProgram
{
public abstract class BindableBase : INotifyPropertyChanged
{
public string ObjectSender { get; set; }
public BindableBase()
{
Task.Run(async () =>
{
OnPropertyChanged(ObjectSender);
});
}
public void OnPropertyChanged(string sender)
{
PropertyChanged(this, new PropertyChangedEventArgs(nameof(sender)));
}
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
}
}
Next we have the viewModel which is where a lot of the code for the main view goes. Basically anything that will be done in the view will go in here. Usually includes ObservableCollection. I am getting confused on what goes in where and why. Also, for I am not quite sure how to go about controlling System.Windows.Controls from here. If you noticed towards the bottom the close function works fine, but trying to get the visibility does not really work.
View Model:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Linq;
using System.Collections.Generic;
namespace MainProgram
{
public partial class TopMenuBarViewModel : BindableBase
{
#region Commands
public ICommand Close { get; }
public ICommand ChangeMenuVisibility { get; }
#endregion
public TopMenuBarViewModel()
{
this.Close = new RelayCommand(CloseWindow);
this.ChangeMenuVisibility = new RelayCommand(ChangedMenuVisibility);
}
private void CloseWindow()
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
MainWindow mainWindow = System.Windows.Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
if (mainWindow != null)
mainWindow.Close();
});
}
private void ChangedMenuVisibility()
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
if (MainWindowViewModel.MenuVisibility == Visibility.Visible)
MainWindowViewModel.MenuVisibility = Visibility.Collapsed;
});
//MainWindowViewModel.MenuVisibility = Visibility.Visible;
}
}
}
This is the view.cs that binds the view and the view model, but I am not sure why this is needed if I have it bound via a DataTemplate. I don't know if I am doing it twice or if they both are needed.
View:
using System.Windows.Controls;
namespace MainProgram
{
/// <summary>
/// Interaction logic for TopMenuBar.xaml
/// </summary>
public partial class TopMenuBar : UserControl
{
public TopMenuBar()
{
InitializeComponent();
var viewModel = new TopMenuBarViewModel();
this.DataContext = viewModel;
}
}
}
This is the XAML for the View TopMenuBar.xaml I have all my bindings correct because I see the commands are executing.
<UserControl x:Class="MainProgram.TopMenuBar"
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:MainProgram"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Button Height="auto" Width="50" Command="{Binding ChangeMenuVisibility}" Background="Transparent" Style="{StaticResource ResourceKey=RoundCorner}" Margin="3,1">
<Grid>
<materialDesign:PackIcon Background="Transparent" Kind="Menu" Height="auto" Width="auto"/>
</Grid>
</Button>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Stretch">
<!--="{Binding CloseWindow}"-->
<Button Height="auto" Width="50" Background="Transparent" Style="{StaticResource ResourceKey=RoundCorner}" Margin="3,1">
<Grid>
<materialDesign:PackIcon Background="Transparent" Kind="Help" Height="auto" Width="auto"/>
</Grid>
</Button>
<Button Height="auto" Width="50" Command="{Binding Close}" Background="Transparent" Style="{StaticResource ResourceKey=RoundCorner}" Margin="3,1">
<Grid>
<materialDesign:PackIcon Background="Transparent" Kind="Power" Height="auto" Width="auto"/>
</Grid>
</Button>
</StackPanel>
</Grid>
</UserControl>
Here is the XAML that I am trying to control the visibility for, but it doesn't let me. This is in the MainWindow.xaml. I have also seen other bits of code that allow people to bind the current view of a usercontrol, like the side bar menu, but I can't seem to get it to work no matter how hard I try.
<Border Visibility="{Binding MenuVisibility}" CornerRadius="5" BorderBrush="Black" BorderThickness="3" Grid.Column="0" Grid.Row="2" Grid.RowSpan="5" Margin="5">
<ccontrols:DeitoContentControl x:Name="MenuSectionView" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<ContentControl.ContentTemplate>
<DataTemplate DataType="{x:Type ViewModellocal:NavigationMenuViewModel}">
<Viewlocal:NavigationMenu/>
</DataTemplate>
</ContentControl.ContentTemplate>
</ccontrols:DeitoContentControl>
</Border>
This is the MainWindowViewModel that has the binding variable in it. ViewModelBase derives from BindableBase.
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace MainProgram
{
public class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged
{
#region Private Members
private UserControl _currentFrame;
private static Visibility _MenuVisibility;
#endregion
#region Constructors
public MainWindowViewModel()
{
}
public UserControl CurrentFrame
{
get { return _currentFrame; }
set
{
if (value == _currentFrame)
return;
_currentFrame = value;
OnPropertyChanged("CurrentFrame");
}
}
public static Visibility MenuVisibility
{
get { return _MenuVisibility; }
set
{
if (_MenuVisibility == value)
return;
_MenuVisibility = value;
//OnPropertyChanged("MenuVisibility");
}
}
public event PropertyChangedEventHandler PropertyChanged = (s, e) => { };
private void OnPropertyChanged(string sender)
{
PropertyChanged(this, new PropertyChangedEventArgs(nameof(sender)));
}
#endregion
}
}
Thank you for reading and stopping by to help out. If there is something I need to change, add, edit just let me know. I didn't show the code for the full MainWindow, but I can if needed. Thanks.
Now, I'm no expert on MVVM, but I have worked with it in the past, so let me try to clarify some things. To anyone more knowledgeable: please feel free to correct me.
This should be the Base Model known as the BindableBase (I Think?) The Model contains the INotifyPropertyChanged, [...]
Since you didn't post the ViewModelBase source I'm going assume it just inherits from BindableBase and doesn't add anything else. In that case the only reason for the separation I could think of is to allow models to inherit from BindableBase.
This is not part of the code idea behind MVVM, models are expected to be completely separate from any ui logic and therefore usually do not implement the INotifyPropertyChanged interface.
(As an aside, it is of course possible to have models implement INotifyPropertyChanged, but as you're trying to understand the core MVVM concepts I think this just adds confusion).
As explained here, the model itself does not contain any logic related to interaction with the UI, but just the data and logic required for the underlying tasks your application is trying to solve.
I usually think of it this way:
If I want to have both a command line version and a UI app, which parts would I move to a library that can be included by both versions?
The answer to that is most likely what should be in your models.
If the application's sole purpose is to experiment with the WPF bindings, it doesn't need any such logic and therefore won't have any models.
Let's take a more detailed look at your BindableBase class. My first advice would be to merge it with the ViewModelBase class and have all view models inherit from it.
Its purpose is to handle everything around the PropertyChanged event so you don't have to include that code in every view model, and it should indeed not contain any fields or properties apart from the PropertyChanged event.
The OnPropertyChanged method is supposed to receive the name of the changed property and call the PropertyChanged event handler. Note that your implementation erroneously always passes the string "sender" as the property name due to the nameof operator. This is most likely why your visibility change events are never received.
The name sender is also usually used to refer to the object firing an event (note how the first parameter of the PropertyChangedEventHandler delegate is called sender and you're passing this).
In addition you might want to look at the CallerMemberName attribute, with it you don't always have to manually specify the property name.
Apart from that I'm a bit confused what the purpose of the ObjectSender property and your constructor is. If I'm not missing anything, ObjectSender will likely be null when the Task in the constructor is run and the PropertyChanged event won't have any subscribers, so nobody is going to act on that fired event anyway.
Applying all of these points, we end up with something like this:
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Next is the TopMenuBarViewModel. It looks mostly fine to me, apart from the fact that it inherits from BindableBase instead of ViewModelBase and I don't see a reason to have a view model partial.
The naming convention I've usually seen implemented is to end all ICommand properties with the word Command, that way you don't run into name conflicts with the implementing method.
I also don't understand why MainWindowViewModel.Visibility is supposed to be static, did you maybe not figure out how else to access it?
TopMenuBarViewModel with proposed changes:
public class TopMenuBarViewModel : ViewModelBase
{
#region Commands
public ICommand CloseWindowCommand { get; }
public ICommand ChangeMenuVisibilityCommand { get; }
#endregion
public TopMenuBarViewModel()
{
this.CloseWindowCommand = new RelayCommand(CloseWindow);
this.ChangeMenuVisibilityCommand = new RelayCommand(ChangeMenuVisibility);
}
private void CloseWindow()
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
MainWindow mainWindow = System.Windows.Application.Current.Windows
.OfType<MainWindow>()
.FirstOrDefault();
if (mainWindow != null)
mainWindow.Close();
});
}
private void ChangeMenuVisibility()
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
// You might want to consider querying the MainWindowViewModel once
// in the constructor and storing it for future use.
MainWindow mainWindow = System.Windows.Application.Current.Windows
.OfType<MainWindow>()
.FirstOrDefault();
if(!(mainWindow.DataContext is MainWindowViewModel mainWindowViewModel))
return; // invalid view model
switch(mainWindowViewModel.MenuVisibility)
{
case Collapsed:
case Hidden:
mainWindowViewModel.MenuVisibility = Visibility.Visible;
break;
case Visible:
mainWindowViewModel.MenuVisibility = Visibility.Collapsed;
break;
}
});
}
}
You mention that you're setting the TopMenuBar's DataContext twice, once in the constructor and once with a DataTemplate. I'm not exactly sure what you mean by "bound via DataTemplate", that code seems to be missing. Do you mean something like this?
<ContentControl>
<ContentControl.ContentTemplate>
<DataTemplate DataType="{x:Type myviewmodels:TopMenuBarViewModel}">
<myviews:TopMenuBar/>
</DataTemplate>
</ContentControl.ContentTemplate>
<myviewmodels:TopMenuBarViewModel/>
</ContentControl>
If yes, it could probably be simplified to something this (it's been a while, so it might not be entirely correct):
<myviews:TopMenuBar>
<myviews:TopMenuBar.DataContext>
<myviewmodels:TopMenuBarViewModel/>
</myviews:TopMenuBar.DataContext>
</myviews:TopMenuBar>
Either way, the XAML-bound DataContext should take precedence, so setting it in the constructor is unnecessary unless you want a "fallback" instance in case it is not set explicitly.
Apart from that, I don't have much to say much about the XAML code. The bindings all look correct to me (command names need to be adjusted if you end up changing them as proposed above).
In case you don't already know about this: you can add the attribute
d:DataContext="{d:DesignInstance Type=myviewmodels:TopMenuBarViewModel}"
to your UserControl start tag, that will give you IntelliSense auto completion for data bindings.
Lastly, your MainWindowViewModel. The whole point of the ViewModelBase base class is that your concrete view models don't have to deal with implementing INotifyPropertyChanged (it is already implemented in the base class).
That means the bottom part of your implementation is not needed.
As mentioned above, I don't see a reason why MenuVisibility is supposed to be static.
Also, note how I removed the explicit string parameters to OnPropertyChanged. This is made possible by the CallerMemberName attribute mentioned above. You could also use nameof(CurrentFrame), but please never just manually write it as a string literal. That will break as soon as you decide to rename one of the properties and is prone to typos.
MainWindowViewModel with proposed changes:
public class MainWindowViewModel : ViewModelBase,
{
private UserControl _currentFrame;
public UserControl CurrentFrame
{
get => _currentFrame;
set
{
if (value == _currentFrame)
return;
_currentFrame = value;
OnPropertyChanged();
}
}
private Visibility _menuVisibility;
public Visibility MenuVisibility
{
get => _menuVisibility;
set
{
if (value == _menuVisibility)
return;
_menuVisibility = value;
OnPropertyChanged();
}
}
}
Wow, this turned into much more of a code review than I originally intended, but I hope there is still some useful information for you in there.
All code in this answer was written from memory, so please excuse any errors I might have made.
I'm learning WPF.
In one of the exercises, I have a TextBox and buttons Cut and Paste. The following is enough to implement Cut and Paste functionality:
XAML:
<DockPanel>
<WrapPanel DockPanel.Dock="Top" Margin="3">
<Button Command="ApplicationCommands.Cut"
CommandTarget="{Binding ElementName=txtEditor}"
Width="60">
_Cut
</Button>
<Button Command="ApplicationCommands.Paste"
CommandTarget="{Binding ElementName=txtEditor}"
Width="60" Margin="3,0">
_Paste<
/Button>
</WrapPanel>
<TextBox AcceptsReturn="True" Name="txtEditor" />
</DockPanel>
When pressed, the button Cut executes the ApplicationCommands.Cut on the TextBox with name txtEditor. When needed, the button will ask the TextBox with name textEditor if it can execute a Cut command, and when pressed it will order the textEditor to execute the Cut command.
Fairly straightforward. It works fine.
Just for Fun, I'd like to implement another button: Clear. When pressed it should clear the TextBox. The Textbox class has a method Clear.
<Button Command="ApplicationCommands.Clear"
CommandTarget="{Binding ElementName=txtEditor}"
Width="60">
Clear
</Button>
Alas, this won't work. ApplicationCommands doesn't have a Clear. Should I implement a custom command, as suggested in this example?
I tried the following:
I implemented CanExecute and Executed methods in my window:
public partial class CustomCommandSample : Window
{
public CustomCommandSample()
{
InitializeComponent();
}
private void ClearCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void ClearCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
txtEditor.Clear();
}
}
A static CustomCommands class:
public static class CustomCommands
{
public static RoutedUICommand Clear => new RoutedUICommand (
"Clear",
"Clear",
typeof(CustomCommands));
}
Finally the XAML:
(Note: the classes in this project are in namespace WpfCommandDemo. Xaml refers to it as Local)
<Window x:Class="WpfTutorialSamples.Commands.UsingCommandsSample"
xmlns="...
xmlns:local="clr-namespace:WpfCommandDemo"
Title="UsingCommandsSample" Height="100" Width="200">
<Window.CommandBindings>
<CommandBinding Command="CustomCommands.Clear"
CanExecute="ClearCommand_CanExecute"
Executed="ClearCommand_Executed" />
</Window.CommandBindings>
<DockPanel>
<WrapPanel DockPanel.Dock="Top" Margin="3">
<Button Command="CustomCommands.Clear"
CommandTarget="{Binding ElementName=txtEditor}"
Width="60">
Clear
</Button>
... (other buttons: cut / paste, as above
</WrapPanel>
<TextBox AcceptsReturn="True" Name="txtEditor" />
</DockPanel>
Although this compiles, The constructor of CustomCommandSample throws an XamlParseException:
Type reference cannot find type named
'{http://schemas.microsoft.com/winfx/2006/xaml/presentation}CustomCommands'.
Should I solve the problem using Custom Commands? What should I change? Or am I completely wrong, and should I solve this differently
To use CustomCommands in XAML, you'll need to add a reference to it. In the element, add a line:
xmlns:custom="clr-namespace:MyApplication.NamespaceWithCustomInIt"
Replacing the namespace value as appropriate. Then you should be able to reference CustomCommands anywhere in XAML as custom:CustomCommands (may have to bind, I'll check later).
Should I solve the problem using Custom Commands?
Yes. This is how to solve this using the Model-View-ViewModel (MVVM) design pattern which is the recommended design pattern to use when developing XAML based UI applications.
From this blog post:
WPF provides two implementations of the ICommand interface; the System.Windows.Input.RoutedCommand and System.Windows.Input.RoutedUICommand where the latter is a subclass of the former that simply adds a Text property that describes the command. However, neither of these implementations are especially suited to be used in a view model as they search the visual tree from the focused element and up for an element that has a matching System.Windows.Input.CommandBinding object in its CommandBindings collection and then executes the Execute delegate for this particular CommandBinding. Since the command logic should reside in the view model, you don’t want to setup a CommandBinding in the view in order to connect the command to a visual element. Instead, you can create your own command by creating a class that implements the ICommand. The below implementation is a common one that invokes delegates for the Execute and CanExecute methods:
public class DelegateCommand: System.Windows.Input.ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public DelegateCommand(Action<object> execute)
: this(execute, null) { }
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null ? true : _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
Once you have an implementation of the ICommand interface, it's easy to use in your view models:
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
ClearCommand = new DelegateCommand(Clear);
}
private string _text;
public string Text
{
get { return _text; }
set { _text = value; NotifyPropertyChanged(); }
}
public ICommand ClearCommand { get; }
private void Clear(object parameter)
{
Text = string.Empty;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
In the view, you simply bind to the properties of the view model:
<TextBox AcceptsReturn="True" Name="txtEditor" Text="{Binding Text}" />
<Button Content="Clear" Command="{Binding ClearCommand}" />
Just remember to set the DataContext of the view to an instance of your view model for the bindings to work:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
In my XAML I am doing the following
<Label Content="{Binding ElementName=Root, Path=UserData.Email, Mode=OneWay}" />
the Root element is my Window itself and the UserData Is a get; private set; auto property in my codebehind file, the Email property is get-only and is of type string.
the UserData object gets set after the user has logged in. But the binding is not taking the value from the object. I have verified that the object does indeed contain the correct data and isn't null. What am I missing here?
I went ahead and created a hello world version for this. Here is the xml. This should simply change the banner when the button is clicked to the text in the text box. I couldn't find a super simple example so I just made one. Obviously there are way more advanced ways to do this but it should make for a simple version to build from.
<Window x:Class="Hello_World.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>
<Label Name="MyLabel" Content="{Binding MyLabel}" HorizontalAlignment="Left" Margin="58,37,0,0" VerticalAlignment="Top" Height="65" Width="423" FontSize="44"/>
<TextBox Name="MyTextBox" HorizontalAlignment="Left" Height="28" Margin="163,162,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="163"/>
<Button Content="Change Banner" HorizontalAlignment="Left" Margin="251,209,0,0" VerticalAlignment="Top" Width="109" Click="Button_Click"/>
</Grid>
</Window>
Next is the ModelView that implements the INotifyPropertyChanged interface. Note that your properties must be public properties with a getter, setter and backing field. This allows you to call the OnPropetyChanged() method whenever the property is set.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Hello_World
{
public class MainViewModel: INotifyPropertyChanged
{
private string _myLabel;
public string MyLabel
{
get { return _myLabel; }
set
{
_myLabel = value;
OnPropertyChanged(nameof(MyLabel));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propetyName)
{
if(PropertyChanged != null)
PropertyChanged(this,new PropertyChangedEventArgs(propetyName));
}
}
}
Lastly the MainWindow. Set the DataContext in the main constructor. Note I could have set the DataContext of the main grid and all of its children would inherit the same DataContext. This would keep you from having to set all of the components' individually.
namespace Hello_World
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MainViewModel MyViewModel;
public MainWindow()
{
InitializeComponent();
MyViewModel = new MainViewModel();
// Here's where I'm setting the object to look at.
DataContext = MyViewModel;
// Now I don't need to access the textbox directly.
MyViewModel.MyLabel = "Hello World";
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// Note: ICommand is a more advanced topic.
MyViewModel.MyLabel = MyTextBox.Text;
}
}
}
I'm using two views which refers same view model. Both of my views contain a text box that binds to a value in the view model. My problem is that, if I change the value of textbox in one GUI, its not reflecting in another. What should I do to achieve this?
This is my view model
public class ProductViewModel:INotifyPropertyChanged
{
private int machineheight;
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
public int MachineHeight
{
get
{
return this.machineheight;
}
set
{
this.machineheight = value;
RaisePropertyChanged("MachineHeight");
}
}
public ProductViewModel()
{
}
private ICommand mUpdater;
public ICommand UpdateCommand
{
get
{
if (mUpdater == null)
mUpdater = new Updater();
return mUpdater;
}
set
{
mUpdater = value;
}
}
private class Updater : ICommand
{
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
SecondWindow w = new SecondWindow();
w.Show();
}
#endregion
}
}
}
The second window is another GUI. Once I click update button, second window opened. But the value that I have changed in first UI is not updated in the new window.
My Xaml is similar for both UI..
<Window x:Class="WPFDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFDemo"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ProductViewModel/>
</Window.DataContext>
<Grid Height="307" Width="480" Initialized="Grid_Initialized">
<Button Content="Update" Height="32" HorizontalAlignment="Left" Margin="165,158,0,0" Name="button1" VerticalAlignment="Top" Width="114" Command="{Binding Path=UpdateCommand}"/>
<TextBox Height="42" HorizontalAlignment="Left" Margin="125,82,0,0" Name="textBox1" VerticalAlignment="Top" Width="169" Text= "{Binding Path= MachineHeight, Mode=TwoWay}" />
</Grid>
</Window>
I actually don't know what is the problem.. thanks
<Window.DataContext>
<local:ProductViewModel/>
</Window.DataContext>
hi, if you put this in your 2 views, then each one has its own viewmodel. so you will never see any changes. you have to set the datacontext from your first view to your second view. Btw for your ICommand implementation look at some mvvm frameworks for easier implementations, eg RelayCommand, DelegateCommand.
For your actual implementation you can add the following to your xaml and ViewModel(CommandParameter) then it works.
<Button Content="Update" Height="32" HorizontalAlignment="Left" Margin="165,158,0,0" Name="button1" VerticalAlignment="Top" Width="114" Command="{Binding Path=UpdateCommand}"
CommandParameter="{Binding .}"/>
public void Execute(object parameter)
{
SecondWindow w = new SecondWindow();
w.DataContext = parameter;
w.Show();
}
There are a hundred things that can go wrong in this scenario, and one of my long-standing gripes with XAML-based databinding is that the MS tools give you precious little help figuring out which of those hundred things it is. This is especially the case if you're new to databinding, but even folks who've been doing it for years can spend obnoxious hours tracking down databinding issues.
Some things to check:
(1) Confirm that your databindings are two-way.
(2) Look in your debug output window to see if there are any error messages there.
(3) Set an IValueConverter in your databinding, and set a breakpoint in the converter to see what data is being passed where and when.
(4) Confirm that the data in the ViewModel is actually being updated.
(5) Confirm that the ViewModel implements INotifyPropertyChanged, and that the PropertyChanged event is firing.
(6) Post your actual code here so folks can look at it.
And so forth.
Hope this helps.
It must work if the ViewModel implements INotifyPropertyChanged.