MVVM - Access command from different class in XAML - c#

In my project I have a TitleView and GameView. When the program launches, TitleView is displayed. The user clicks a button and GameView is displayed. I am using MVVM Light which includes MainViewModel which has commands to switch to the desired views:
From MainViewModel.cs
GameViewCommand = new RelayCommand(() => ExecuteGameViewCommand());
private void ExecuteGameViewCommand()
{
CurrentViewModel = MainViewModel._gameViewModel;
}
In TitleView.xaml, I need to access this command and I don't know how. I am very much a novice when it comes to XAML.
From TitleView.xaml
<UserControl x:Class="AoW.Views.TitleView"
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:vm="clr-namespace:AoW.ViewModels"
mc:Ignorable="d"
d:DesignWidth="1020"
d:DesignHeight="740"
Width="1020"
Height="740">
<Button Content="New Game"
//This needs to bind to GameViewCommand in MainViewModel.cs
Command="{Binding GameViewCommand}"
Grid.Column="1"
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
If I put the following line into TitleView.xaml...
DataContext="{Binding Main, Source={StaticResource Locator}}"
... I can access GameViewCommand, but I can't access any local commands.
What can I do to gain access to GameViewCommand while maintaining control of local commands?
If providing additional code would be helpful, please let me know.

I've fixed the problem. All I had to was this:
<Button Content="New Game"
Command="{Binding GameViewCommand}"
DataContext="{Binding Main, Source={StaticResource Locator}}"
Grid.Column="1"
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<Button Content="Say Hello"
Command="{Binding SayHelloCommand}"
Grid.Column="2"
Grid.Row="2"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
Turns out it was a very easy fix.

Related

C# UWP: The parameter is incorrect - NavigationView

I'm building a UWP in C# with Visual Studio, and need to hide and show a NavigationView, named 'navigationView' as required.
I am using the property .IsPaneVisible to achieve this, but am encountering a strange bug:
I can set IsPaneVisible = true or false in the xaml document, but if I set navigationView.IsPaneVisible = false; in the related C# document, and run a debug build of the app from Visual Studio, the app crashes with the error
System.ArgumentException: 'The parameter is incorrect.
Even stranger, if I run the same debug build, but launched directly, outside Visual Studio, the app functions normally without crashing at this line.
Does anyone have an idea of why this might be happening or how I could fix it?
Here's the xaml code:
<Page
x:Class="Sheet_Music_Reader.Views.ShellPage"
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:i="using:Microsoft.Xaml.Interactivity"
xmlns:behaviors="using:Sheet_Music_Reader.Behaviors"
xmlns:winui="using:Microsoft.UI.Xaml.Controls"
xmlns:helpers="using:Sheet_Music_Reader.Helpers"
xmlns:views="using:Sheet_Music_Reader.Views"
Loaded="OnLoaded"
mc:Ignorable="d">
<Page.Resources>
<!--This top margin is the height of the custom TitleBar-->
<Thickness x:Key="NavigationViewContentMargin">0,48,0,0</Thickness>
<Thickness x:Key="NavigationViewContentGridBorderThickness">0</Thickness>
<SolidColorBrush x:Key="NavigationViewContentBackground" Color="Transparent"></SolidColorBrush>
</Page.Resources>
<Grid>
<Border x:Name="AppTitleBar"
IsHitTestVisible="True"
VerticalAlignment="Top"
Background="Transparent"
Height="40"
Canvas.ZIndex="1"
Margin="48,8,0,0">
<StackPanel Orientation="Horizontal">
<Image x:Name="AppFontIcon"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Source="Assets/Square44x44Logo.png"
Width="16"
Height="16"/>
<TextBlock x:Name="AppTitle"
Text="enScore"
VerticalAlignment="Center"
Margin="12,0,0,0"
Style="{ThemeResource CaptionTextBlockStyle}"/>
</StackPanel>
</Border>
<winui:NavigationView
IsBackButtonVisible="Visible"
IsBackEnabled="{x:Bind IsBackEnabled, Mode=OneWay}"
SelectedItem="{x:Bind Selected, Mode=OneWay}"
ItemInvoked="OnItemInvoked"
IsSettingsVisible="True"
IsTitleBarAutoPaddingEnabled="False"
DisplayModeChanged="NavigationViewControl_DisplayModeChanged"
Canvas.ZIndex="0"
x:Name="navigationView">
<winui:NavigationView.MenuItems>
<winui:NavigationViewItem x:Uid="Shell_Main" Icon="Copy" helpers:NavHelper.NavigateTo="views:MainPage" />
</winui:NavigationView.MenuItems>
<i:Interaction.Behaviors>
<behaviors:NavigationViewHeaderBehavior
DefaultHeader="{x:Bind Selected.Content, Mode=OneWay}">
<behaviors:NavigationViewHeaderBehavior.DefaultHeaderTemplate>
<DataTemplate>
<Grid x:Name="headerGrid">
<TextBlock
Text="{Binding}"
Style="{ThemeResource TitleTextBlockStyle}"
Margin="{StaticResource SmallLeftRightMargin}" />
<Button Style="{ThemeResource ButtonRevealStyle}" Content="Refresh Library Contents" HorizontalAlignment="Right" Margin="0,45,8,0" Click="RefreshLibrary"/>
<Button Style="{ThemeResource ButtonRevealStyle}" Content="Import PDF To Current Folder" HorizontalAlignment="Right" Margin="0,-35,140,0" Click="AddScore"/>
<Button Style="{ThemeResource ButtonRevealStyle}" Content="Add New Folder" HorizontalAlignment="Right" Margin="0,-35,8,0" Click="AddFolderAsync"/>
<ToggleButton Style="{ThemeResource ToggleButtonRevealStyle}" x:FieldModifier="public" x:Name="tbtndelete" Content="Delete Items" HorizontalAlignment="Right" Margin="0,45,191,0" Tapped="DeleteItem"/>
</Grid>
</DataTemplate>
</behaviors:NavigationViewHeaderBehavior.DefaultHeaderTemplate>
</behaviors:NavigationViewHeaderBehavior>
</i:Interaction.Behaviors>
<Grid>
<Frame x:Name="shellFrame" />
</Grid>
</winui:NavigationView>
</Grid>
</Page>
Try to change the placement of the menu with:
PaneDisplayMode="Top"
For my case with Microsoft.UI.Xaml.Controls for Microsoft.WindowsAppSDK v1.0.0.
the showing/hidding of menu bar works fine.
Left, LeftMinimal, LeftCompact crashes the app.
I know you may expect the menu on the left, but still on the top is not that bad idea ;-) At least it works.. Greetz.

Proper way of specifying DataContext (WPF)

I just started working with WPF. In my new application I implemented notify icon with context menu first. Next I started building MVVM framework and found that the new changes impact the code already implemented.
I am using NotifyIcon from Hardcodet. My initial version was something like this:
<Window x:Class="ScanManager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpf="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:tb="http://www.hardcodet.net/taskbar"
xmlns:commands="clr-namespace:ScanManager.Commands"
Title="Scan" Height="542" Width="821">
<Grid Visibility="Visible" Loaded="form_Loaded">
...
<tb:TaskbarIcon HorizontalAlignment="Left" Margin="357,537,0,0" Name="mainTaskbarIcon" VerticalAlignment="Top" IconSource="/Icons/TestIcon.ico" IsHitTestVisible="True" ToolTipText="Test Test" >
<tb:TaskbarIcon.ContextMenu>
<ContextMenu>
<MenuItem Header="_Show" Command="{commands:ShowMainWindowCommand}" CommandParameter="{Binding}" />
<MenuItem Header="_Hide" Command="{commands:HideMainWindowCommand}" CommandParameter="{Binding}" />
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
<Button Name="hideButton" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click" />
</Grid>
</Window>
Next I started incorporating MVVM pattern based on article The World's Simplest C# WPF MVVM Example. The example project adds DataContext pointing to a ViewModel class.
<Window.DataContext>
<ViewModels:Presenter/>
</Window.DataContext>
This change affected the way the notification icon works. In a nutshell, the overriding methods ICommand.CanExecute(object parameter) and ICommand.Execute(object parameter) of the ShowMainWindowCommand and HideMainWindowCommand objects started receiving object Presenter defined in Window.DataContext instead of original Hardcodet.Wpf.TaskbarNotification.TaskbarIcon. And I am guessing this is because the added DataContext affects the {Binding} value of the CommandParameter.
The Execute method expects the parameter to be TaskbarIcon in order to identify the parent Window object, which then can be set shown or hidden.
The way I was trying to address it I moved all elements but the TaskbarIcon from Window to a UserControl, under a Grid and applied DataContext to the Grid
<UserControl x:Class="ScanManager.Views.SControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
...
d:DataContext="{d:DesignInstance ViewModels:Presenter}">
<Grid Visibility="Visible">
<Grid.DataContext>
<ViewModels:Presenter/>
</Grid.DataContext>
<Button Name="hideButton" Command="{Binding Path=HideMainWindowCommand}" CommandParameter="{Binding}" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click" />
...
</Grid>
</UserControl>
It addressed the issue with notify icon, but I am wondering if this is right way of resolving the situation. I thought the other way could be to set CommandParameter in MenuItem in the original version after DataContext was added, to proper value, however I am having hard time figuring this out.
As the next step, I am trying to cast DataContext of the UserControl object to INotifyPropertyChanged in order to subscribe to PropertyChanged event, however the DataContext property comes in as null, presumable because it was set only to Grid and not to the UserControl:
INotifyPropertyChanged viewModel = (INotifyPropertyChanged)this.DataContext;
Any guidance on putting these pieces together properly would be much appreciated.
Edit
Access Denied, these options are helpful for the Button element.
What if I would like to come back to the initial version at the top, the MenuItem element uses Command="{commands:ShowMainWindowCommand}" and CommandParameter="{Binding}". If I add Window.DataContext, is there a change that can be done to the MenuItem's Command/CommandParameter attributes in order to reference what they referred before (I assume, the parent element)? I tried CommandParameter="{Binding Path=mainTaskbarIcon}" and it did not work meaning, like before, the Execute/CanExecute receive null.
<Window x:Class="ScanManager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpf="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:tb="http://www.hardcodet.net/taskbar"
xmlns:commands="clr-namespace:ScanManager.Commands"
Title="Scan" Height="542" Width="821">
<Window.DataContext>
<ViewModels:Presenter/>
</Window.DataContext>
<Grid Visibility="Visible" Loaded="form_Loaded">
...
<tb:TaskbarIcon HorizontalAlignment="Left" Margin="357,537,0,0" Name="mainTaskbarIcon" VerticalAlignment="Top" IconSource="/Icons/TestIcon.ico" IsHitTestVisible="True" ToolTipText="Test Test" >
<tb:TaskbarIcon.ContextMenu>
<ContextMenu>
<MenuItem Header="_Show" Command="{commands:ShowMainWindowCommand}" CommandParameter="{Binding Path=mainTaskbarIcon}" />
<MenuItem Header="_Hide" Command="{commands:HideMainWindowCommand}" CommandParameter="{Binding Path=mainTaskbarIcon}" />
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
...
</Grid>
</Window>
When you set datacontext it spreads to the inner controls as well and yes it affects Binding context. There is no need to create UserControl since it does not prevent context from spreading. In order to prevent it change datacontext of the control or specify binding source. For example, if you want to change context of the button.
Approach with DataContext override:
<Grid Visibility="Visible">
<Grid.Resources>
<ViewModels:Presenter x:Key="buttonContext"/>
</Grid.Resources>
<Button DataContext="{StaticResource buttonContext}" Name="hideButton" Command="{Binding Path=HideMainWindowCommand}" CommandParameter="{Binding}" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click"/>
Approach with specifying source:
<Grid.Resources>
<ViewModels:Presenter x:Key="buttonContext"/>
</Grid.Resources>
<Button Name="hideButton" Command="{Binding Source={StaticResource buttonContext}, Path=HideMainWindowCommand}" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click"/>
Or you can also have ButtonContext property in your root viewModel and resolve it this way:
<Button DataContext="{Binding ButtonContext}" Name="hideButton" Command="{Binding Path=HideMainWindowCommand}" CommandParameter="{Binding}" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click"/>
How to subscribe to DataContextChanged event:
public MainWindow()
{
InitializeComponent();
DataContextChanged += MainWindow_DataContextChanged;
Handle event:
private void MainWindow_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null && e.OldValue is INotifyPropertyChanged)
{
((INotifyPropertyChanged)e.OldValue).PropertyChanged -= MainWindow_PropertyChanged;
}
if (e.NewValue != null && e.NewValue is INotifyPropertyChanged)
{
((INotifyPropertyChanged)e.NewValue).PropertyChanged += MainWindow_PropertyChanged;
}
}
private void MainWindow_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
...
}
You don't have to adhere to Commanding. Change your menu items to use a click event instead, and they can call command operation from the View's codebehind.
Add Click="{Your click event name}"
F12 on the click event to create/go to the event.
As an aside here is a way to bind a VM to a data contect without doing it in the XAML.
Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding

WPF- How to hide a dropdown menu after click

I have a SplitButton in my WPF window, which is borrowed from Xceed's Extended WPF Toolkit. Its dropdown content is consisted of some RadioButtons. Something like:
<Window x:Class="WpfTest.Test3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tk="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit"
Title="Test3" Height="300" Width="300">
<Grid Height="25" Width="150">
<tk:SplitButton Content="Default Command">
<tk:SplitButton.DropDownContent>
<StackPanel>
<RadioButton Content="Default Command" GroupName="variations" Margin="5" IsChecked="True"/>
<RadioButton Content="Alternate Command 1" GroupName="variations" Margin="5"/>
<RadioButton Content="Alternate Command 2" GroupName="variations" Margin="5"/>
</StackPanel>
</tk:SplitButton.DropDownContent>
</tk:SplitButton>
</Grid>
</Window>
which generates something like this:
The problem is, when I click on each of the RadioButtons the dropdown menu doesn't dissappear. I did some googling and realized that I should handle the Click event for each RadioButton. But I don't know how to hide the dropdown menu in that event handler. As a side-note, it seems a MenuItem has the property of StaysOpenOnClick, but there is no such thing for other controls.
Although doing this programmatically would suffice, but is there an MVVM way for this?
Add Checked event on your radio button and use SplitoButton.IsOpen=false;. Follow this code.
Xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tk="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit"
Title="MainWindow" Height="350" Width="525">
<Grid>
<tk:SplitButton Name="SplitButton" Content="Default Command">
<tk:SplitButton.DropDownContent>
<StackPanel>
<RadioButton Checked="rb_Checked" Content="Default Command" GroupName="variations" Margin="5" IsChecked="True"/>
<RadioButton Checked="rb_Checked" Content="Alternate Command 1" GroupName="variations" Margin="5"/>
<RadioButton Checked="rb_Checked" Content="Alternate Command 2" GroupName="variations" Margin="5"/>
</StackPanel>
</tk:SplitButton.DropDownContent>
</tk:SplitButton>
</Grid>
</Window>
.cs
private void rb_Checked(object sender, RoutedEventArgs e)
{
SplitButton.IsOpen = false;
}

Error template not showing for controls within a page

I have a textbox within a page that binds to a model property that is validated, and when there is an error the error template is not shown!
A few points:
I have no validation issues for controls in there own window (not within a page displayed in a frame within a window).
The error template works, it is displayed for controls within a window.
The model is validating because the "Save" button within the page is disabled when a validation error is identified.
I "think" the problem lies with the fact that the control is within a page that is hosted within frame and thus the data context is not being passed to the page as it is seemingly isolated. Could that be the case? and if so how do I go about fixing this issue? and if not what else could it be?
The code (I have of course simplified the code to isolate the issue):
<Page x:Class="PIRS_Client.View.Staff.StaffDetailsView"
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:ignore="http://www.ignore.com"
mc:Ignorable="d ignore"
DataContext="{Binding StaffDetailsVM, Source={StaticResource Locator}}"
Height="576" Width="1163">
<Grid>
<TextBox HorizontalAlignment="Left" Text="{Binding Model.title, ValidatesOnDataErrors=True}" Height="17" Margin="284,453,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="90"/>
<Button Content="Save Changes" Command="{Binding SaveDetailsCommand}" IsEnabled="{Binding Model.IsValid}" HorizontalAlignment="Left" Margin="1007,518,0,0" VerticalAlignment="Top" Width="104" Height="23"/>
</Grid>
If I can add any further information or code please just let me know!
Problem solved - it transpires that Windows have a built in adorner layer but Pages do not, I would love to give you a detailed explanation as to why not but I don't know why (read below). But the solution is that you need to wrap your page contents in an adorner decorator to provide them with an adorner layer.
Just to be clear here is the "fixed" code:
<Page x:Class="PIRS_Client.View.Staff.StaffDetailsView"
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:ignore="http://www.ignore.com"
mc:Ignorable="d ignore"
DataContext="{Binding StaffDetailsVM, Source={StaticResource Locator}}"
Height="576" Width="1163">
<AdornerDecorator>
<Grid>
<TextBox HorizontalAlignment="Left" Text="{Binding Model.title, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Height="17" Margin="284,453,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="90"/>
<Button Content="Save Changes" Command="{Binding SaveDetailsCommand}" IsEnabled="{Binding Model.IsValid}" HorizontalAlignment="Left" Margin="1007,518,0,0" VerticalAlignment="Top" Width="104" Height="23"/>
</Grid>
</AdornerDecorator>
If you know why pages don't implement the adorner layer and can provide a more insightful answer please feel free to add an explanation as another answer which contains the solution i.e. this and the explanation and I will change your answer to be the correct answer (instead of mine) - just to promote sharing knowledge ;)

How to bind an element to a property which belongs to the root element of a control?

The question may sound a little confusing, but the problem I'm currently facing is this:
<Button x:Class="sandbox.BtnLabel"
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"
x:Name="this">
<Button.ToolTip>
<TextBlock Background="Yellow" Text="{Binding ElementName=this, Path=LabelText}"/>
</Button.ToolTip>
<TextBlock Background="Yellow" Text="{Binding ElementName=this, Path=LabelText}"/>
</Button>
Only the second binding works, which sets the content of the button. The first one, which I would like to use to set the contents of the tooltip of the button (via the LabelText dependency property) does not work.
Is it possible to make the first binding work?
Thanks.
Try this:
<Button x:Class="sandbox.BtnLabel"
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"
x:Name="this">
<Button.ToolTip>
<ToolTip DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
<TextBlock Background="Yellow"
Text="{Binding LabelText}" />
</ToolTip>
</Button.ToolTip>
<TextBlock Background="Yellow"
Text="{Binding ElementName=this,
Path=LabelText}" />
</Button>
We add a ToolTip element and assign it's DataContext as it's PlacementTarget which should then reach the TextBlock

Categories