Understanding each MVVM Component - c#

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.

Related

How to pass commands across different XAML files/controls

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.

Learning MVVM. Calling Methods

I'm currently learning how MVVM works and gettings a bit confused.
What I Have Now: I've got a MainWindow.xaml and have made a button that adds in UserControl1.xaml adding it to a ContentControl, which all works great. I've got a folder named ViewModels with a class named SettingsViewModel.cs and another folder named Views with a UserControl named SettingsView.xaml
What I'm trying to figure out: In the User Control I'll have things like buttons, checkboxes, and some other stuff, I want to be able to have a button press in the MainWindow to call a method where I can do stuff like changing the visibility of items among other things. How I go about calling this method from the MainWindow and where to put the method [SettingsViewModels.cs or SettingsView.xaml].
I'm still very new to programming so I'm probability leaving out a bunch of info, so ask me any question.
I have accually got this to work the other way around; calling a method in MainWindow from a UserControl like this...
//this is in the UserControl
private void Button1_Click(object sender, RoutedEventArgs e)
{
MainWindow callMethod = (MainWindow)Application.Current.MainWindow;
callMethod.MyMethod1();
}
//this is in the MainWindow
pubic void MyMethod1()
{
//whatevery i want here
}
There are a couple of things to consider. In MVVM, View communicate to ViewModel through bindings and ViewModel communicate to the View through events typical from INotifyPropertyChanged and ICollectionChanged. Buttons should be binded to a property of type ICommand. The ViewModel should not know about WPF control stuff like Visibility etc.
To change visibility you use an IValueConverter called BooleanToVisiblityConverter.
Without quite understanding what you are asking, here is some pseudo code of how I would do it.
The structure of your files doesn't matter, but dividing them into Views and ViewModels is a good idea.
Disclaimer: This code will not run, shows only the concept. I left Visual Studio on my other computer.
ViewModel:
public class MainWindowViewModel
{
public ICommand OpenCommand { get; }
public object Child { get; private set; }
public MainWindowViewModel()
{
OpenCommand = new RelayCommand(Open);
}
private void DoOpen()
{
Child = new ChildViewModel();
}
}
public class ChildViewModel
{
public bool ShowSomething { get; }
}
public class Program
{
private void SomeStartupLogic()
{
var window = new MainWindow();
windows.DataContext = new MainWindowViewModel(); // or use an IoC container
window.Show();
}
}
View
<Window class="MainWindow">
<Window.Resources>
<DataTemplate DataType="{x:Type ChildViewModel}">
<ChildView/>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding Child}"/>
<Button Command="{Binding OpenCommand}"/>
</Grid>
</Window>
<UserControl class="ChildView">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConvert"/>
</UserControl.Resources>
<Grid>
<TextBlock Text="Something" Visibility="{Binding ShowSomething, Converter={StaticResource BooleanToVisibilityConvert}/>
</Grid>
</UserControl>
Links
MVVM
Commands
PropertyChanged

WPF, MVVM and PRISM - No Parameterless Constructor defined for this object

Answer
OK so adding the suggested code given by E-Bat didn't have any affect until I started a new project and copied all the code across verbatim. I can only assume there must be some background code within the ViewModelLocator on http://prismlibrary.com/ which did not update to take the parameterless constructor into account. Hope this helps anyone else with the same issue
Original Question
I have set up a MVVM project using prism. I have a MainWindow.xaml and 5 Views; ButtonsView, HeaderView, ProcessInputView, ProcessLogView and ProcessSelectionView which I am using, each View has an associated ViewModel.
MainWindow.xaml
<Window x:Class="TransactionAutomationTool.Views.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:TransactionAutomationTool"
xmlns:views="clr-namespace:TransactionAutomationTool.Views"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="800">
<Grid>
<views:HeaderView x:Name="HeaderViewControl" Margin="20,21,0,0" />
<views:ProcessSelectionView x:Name="ProcessSelectionViewControl" Margin="20,119,0,0" />
<views:ProcessInputView x:Name="ProcessInputViewControl" Margin="20,280,0,0" />
<views:ProcessLogView x:Name="ProcessLogView" Margin="298,105,0,0" />
<views:ButtonsView x:Name="ButtonViewControl" Margin="0,513,0,0" />
</Grid>
MainWindowViewModel
public class MainWindowViewModel: BindableBase
{
public IEventAggregator _events;
private UserPrincipal userPrincipal;
public MainWindowViewModel(IEventAggregator events)
{
_events = events;
userPrincipal = UserPrincipal.Current;
_events.GetEvent<HeaderLoaded>().Subscribe(HeaderHasBeenLoaded);
}
private void HeaderHasBeenLoaded()
{
_events.GetEvent<UserNameUpdate>().Publish(string.Format("{0} {1}", userPrincipal.GivenName, userPrincipal.Surname));
}
}
When I try to view the MainWindow in design mode I get the following Error
Screenshot of MainWindow In design Mode
No Parameterless constructor found for this object - This Highlights the HeaderView and the ButtonsView
Both the HeaderViewModel and ButtonsViewModel take IEventAggregator as a parameter within their constructor where as the rest of the ViewModels do not. I am assuming this is where the errors are coming from.
HeaderViewModel
public class HeaderViewModel: BindableBase
{
private string userName;
private string runTime;
public string UserName
{
get { return userName; }
set { SetProperty(ref userName, value); }
}
public string RunTime
{
get { return runTime; }
set { SetProperty(ref runTime, value); }
}
public HeaderViewModel(IEventAggregator events)
{
events.GetEvent<RunTimeUpdate>().Subscribe(RunTimeUpdated);
events.GetEvent<UserNameUpdate>().Subscribe(UserNameUpdated);
events.GetEvent<HeaderLoaded>().Publish();
}
private void RunTimeUpdated(string newRunTime)
{
RunTime = newRunTime;
}
private void UserNameUpdated(string userName)
{
UserName = userName;
}
}
So how can I get round this error if I need to subscribe to these events and hence need the IEventAggregator passed into my ViewModels?
Do I need to register this within the Bootstrap via an override of the ConfigureContainer method? If so I'm not entirely sure how to do this.
Bootstrap
class Bootstraper: UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow.Show();
}
}
The application builds successfully and runs successfully but it is just when trying to view the MainWindow in the designer that I get this message.
Any help would be greatly appreciated.
EDIT
All my view constructors just have the initalizeComponent methods and take no parameters
The answer marked as accepted addresses the exception, but doesn't answer the question about why. Also, that approach will make unit testing quite difficult as you will be setting the datacontext to a specific object instead of passing in a dependency.
The reason you are getting the exception is because the HeaderView is not being instantiated by the container(by default it's UnityContainer).
You are constructing the entirety of MainWindow at design time instead of individual pieces. Try the following in MainWindow
<Grid>
<Grid.RowDefinitions>
<RowDefinitions />
<RowDefinitions />
<RowDefinitions />
<RowDefinitions />
<RowDefinitions />
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" prism.RegionManager.RegionName="Row0Region" />
<ContentControl Grid.Row="1" prism.RegionManager.RegionName="Row1Region" />
<ContentControl Grid.Row="2" prism.RegionManager.RegionName="Row2Region" />
<ContentControl Grid.Row="3" prism.RegionManager.RegionName="Row3Region" />
<ContentControl Grid.Row="4" prism.RegionManager.RegionName="Row4Region" />
</Grid>
Then you can either use View Discovery or View Injection. For View Discovery, you can do something like
this.RegionManager.RegisterViewWithRegion("Row0Region", HeaderView()) and so on for rest of the views.
You can register views with regions in initialize method of modules or somewhere else. Upto you as to where you do that. You could override the Run method of the bootstrapper. Once the base run method has finished, you can register your views.
When the main window is displayed, all the regions will be discovered and RegionManager will populate the regions with the views that have been registered with each of the regions.
The regionmanager will instantiate the views using the container. When the container constructs each of the views, their viewmodels will be auto wired up. Also the IEventAggregator will be provided to the HeaderView's viewmodel.
This article is based on prism 4 - https://www.codeproject.com/Articles/165376/A-Prism-Application-Checklist but it talks about how to construct views.
Your view is trying to execute logic that only make sense at runtime, so you need to make sure that you are not in design mode:
public HeaderView()
{
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
{
var svc = ServiceLocator.Current;
var eventAggregator = svc.GetInstance<IEventAggregator>();
this.DataContext = new HeaderViewModel(eventAggregator);
}
InitializeComponent();
}
EDIT:
For support of designtime view model have a look here
Basically you need to provide a parameterless constructor for you ViewModel to support design mode.

C# WPF non-static commands implementation possible?

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.

ContentControl not updating

I'm trying to have a MainWindow that is bound to the a view. I change that view in code and expect it to update in the Main Window, however that is not happening.
I have this code in my XAML
<Grid>
<ContentControl Content="{Binding Source={StaticResource ViewModelLocator}, Path=MainWindowViewModel.CurrentControl}" />
</Grid>
I then change my Control via this code
public class MainWindowViewModel : ReactiveObject
{
private UserControl _CurrentControl = null;
public UserControl CurrentControl
{
get
{
if (_CurrentControl == null)
{
_CurrentControl = new HomePage();
}
return _CurrentControl;
}
set
{
this.RaiseAndSetIfChanged(x => x.CurrentControl, value);
}
}
}
As you can see I'm using the ReactiveUI library.
Is ContentControl the wrong thing to use in that view or am I just not binding and updating correctly?
There is actually a far better way to do this, using ViewModelViewHost:
<Grid DataContext="{Binding ViewModel, ElementName=TheUserControl}">
<ViewModelViewHost ViewModel="{Binding CurrentControlViewModel}" />
</Grid>
Now, your class will look something like:
public class MainWindowViewModel : ReactiveObject
{
private ReactiveObject _CurrentControlViewModel = new HomePageViewModel();
public ReactiveObject CurrentControlViewModel {
get { return _CurrentControl; }
set { this.RaiseAndSetIfChanged(x => x.CurrentControlViewModel, value); }
}
}
And somewhere in your app's startup, you should write:
RxApp.Register(typeof(IViewFor<HomePageViewModel>), typeof(HomePage));
What's ViewModelViewHost?
ViewModelViewHost will take a ViewModel object that you provide via Bindings, and look up a View that fits it, using Service Location. The Register call is how you can associate Views with ViewModels.
why you call your class MainWindowViewModel? when you wanna do mvvm you shouldn't have properties with type UserControl in your VM.
the usual mvvm way looks like this:
viewmodel with INotifyPropertyChanged
public class MyViewmodel
{
public IWorkspace MyContent {get;set;}
}
xaml content control with binding to your VM
<ContentControl Content="{Binding MyContent}"/>
datatemplate --> so that wpf knows how to render your IWorkspace
<DataTemplate DataType="{x:Type local:MyIWorkSpaceImplementationType}" >
<view:MyWorkspaceView />
</DataTemplate>
I think you have several muddled concepts here and they are getting in each others way.
Firstly you aren't actually using ANY of the reactiveUI code, it never gets called. Since your get accessor implements a lazy instantiation pattern then it means the set accessor is ignored. This means that the view never notifies the property change, so you never get updates.
I'd recommend using something more like
private UserControl _currentControl;
public MainWindowVirwModel()
{
CurrentControl = new HomePage();
}
public UserControl CurrentControl
{
get { return _curentControl;}
set { this.RaiseAndSetIfChanged(...); }
}
In addition, this still mixes up View components i.e. HomePage, inside your ViewModel tier which will making unit testing far more difficult.

Categories

Resources