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
So I have a contextmenu in my xaml code which is
<ListView x:Name="listView" Grid.Row="1" SelectionChanged="listView_SelectionChanged" IsEnabled="True">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Command="Delete" IsEnabled="True" Click="MenuItem_Click"></MenuItem>
</ContextMenu>
</ListView.ContextMenu>
</ListView>
and then in my c# code I have
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
listView.Items.Remove(listView.SelectedItems[0]);
showList.RemoveAt(listView.Items.IndexOf(listView.SelectedItems[0]));
}
but unfortunatelly when I debug my program It won't let me click contextmenu delete button :( How can I solve this?
I have a Datagrid in Silverlight application. The user is able to get the focus on Datagrid using Tab key and move between various rows using Up and Down arrow Key.
Please advice, how to fire row select event when the user hits the Spacebar Key for an selected row.
Below is the code snippet:
<Custom:ClientControl
x:Class="TestNamespace.Modules.Views.SampleView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<sdk:DataGrid x:Name="dg" ...>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding DoSomething}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<sdk:DataGrid.Columns>
...
Apprently the solution turned out to be very smiple.
Step 1: Add KeyDown to the Datagrid.
<sdk:DataGrid x:Name="dg" KeyDown="dg_KeyDown">
Step 2: In .XAML.CS file inside Datagrid KeyDown event invoke the method which handles MouseLeftButtonUp event.
private void dg_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.Space)
{
this.viewModel.DoSomething();
}
}
Try this:
<DataGrid>
<DataGrid.InputBindings>
<KeyBinding Key="Space" Command="{Binding DoSomething}"/>
</DataGrid.InputBindings>
</DataGrid>
You can bind the selected value to a property in your View model.
Thanks for reading my thread.
I am trying to insert a cancel button that basically does the X button for a window.
Here is the code from How would I make a cancel button work like the "X" button?. I have exactly the same situation
public partial class Dialog : Window
{
.
.
.
private void Window_Closing(object sender, CancelEventArgs e)
{
e.Cancel() = true; //Works as expected
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
e.Cancel() = true; //Compile error
}
}
Here is the xaml:
<Window x:Class="ExperimentSettingsViewer.TemplateName"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Template Name"
Height="120"
Width="300"
WindowStartupLocation="CenterOwner">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/ExperimentSettingsViewer;component/Button.xaml" />
<ResourceDictionary Source="/ExperimentSettingsViewer;component/Window.xaml" />
<ResourceDictionary Source="/ExperimentSettingsViewer;component/Border.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox Name="tbName"
Margin="3"
Text="{Binding Path=NewName}" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="right">
<Button Name="btnOK"
Content="OK"
Width="75"
Height="30"
HorizontalAlignment="Right"
Margin="3"
Click="btnOK_Click" />
<Button Name="btnCancel"
Content="Cancel"
Width="75"
Height="30"
HorizontalAlignment="Right"
Margin="3"
Click="btnCancel_Click" IsCancel="True" />
</StackPanel>
</StackPanel>
</Grid>
</Window>
However, I follow the solution in that thread, and set the IsCancel of the buttom to true, but still, there is no Cancel() methods available for my button.
I am wondering is there anything I missed? Or how can I make my cancel button work like a X button. Thanks a lot.
The Button.IsCancel property does not automatically close your window. All it does is allow you to use Escape to "press" the button. You still need to call Close() in your click event handler in order to close the window.
Because of this, there is no Cancel property for that event. If you don't want to close the window, just don't call Close().
I must be doing something stupid here but I cant get a MouseDown event to fire when I am clicking on the UserControl. Driving me Mad.
Here's the XAML for the UserControl:
<UserControl x:Name="cusTextBox" x:Class="StoryboardTool.CustomTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" MouseDown="cusTextBoxControl_MouseDown">
<Grid>
<RichTextBox x:Name="richTextBox">
<RichTextBox.ContextMenu>
<ContextMenu>
<MenuItem x:Name="ContextMenuBringForward" Header="BringForward" Click="ContextMenuBringForward_Click"/>
<MenuItem x:Name="ContextMenuSendBackward" Header="SendBackward" Click="ContextMenuSendBackward_Click"/>
</ContextMenu>
</RichTextBox.ContextMenu>
</RichTextBox>
</Grid>
</UserControl>
Code Behind:
private void cusTextBoxControl_MouseDown(object sender, MouseButtonEventArgs e)
{
selected = (CustomTextBox)sender;
}
Why wont this fire when I am clicking the User Control?
You MouseDown is handled by RichTextBox. Use PreviewMouseDown instead.