How to bind hotkeys to generic buttons? - c#

I have a Usercontrol that creates buttons at run-time. I have made the buttons work with command bindings by clicking the buttons is there a way to trigger the buttons using hotkeys e.g ctrl+R and once I get to the TakeC command I need to know what command was pressed?
XAML:
<UserControl.InputBindings>
<KeyBinding Command="{Binding TakeC, Source=self}"
CommandParameter="{Binding }"
Gesture="CTRL+R" />
</UserControl.InputBindings>
<StackPanel Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding CButtons}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Name="CButtonsPanel" CanVerticallyScroll="true" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Name="cTakeC" Content="{Binding Content}" Command="{Binding Path=TakeCCommand}" CommandParameter="{Binding}" Margin="5">
<FrameworkElement.Style>
<MultiBinding Converter="{StaticResource CButtonStyleConverter}">
<MultiBinding.Bindings>
<Binding/>
<Binding Path="IsActive"/>
</MultiBinding.Bindings>
</MultiBinding>
</FrameworkElement.Style>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
C#:
TakeCCommand = new RelayCommand(TakeC);
void TakeC(object parameter)
{
ButtonViewModel<StyledButtonModel> myClass = parameter as ButtonViewModel<StyledButtonModel>;
// All buttons gets here once clicked
// I need to know what key was pressed here
}
public class StyledButtonModel
{
public string Name { get; set; } = "Ctrl+R"
public CButtonStyle Styles { get; set; }
}

KeyBindings will only work when the element to which you have applied them is focused:
Keybindings without any focus
Depending on your requirements, a better approach may be to handle the PreviewKeyDown event for the parent window of the UserControl. Something like this:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
Loaded += UserControl1_Loaded;
Unloaded += UserControl1_Unloaded;
}
private void UserControl1_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= UserControl1_Loaded;
Window window = Window.GetWindow(this);
window.PreviewKeyDown += Window_PreviewKeyDown;
}
private void UserControl1_Unloaded(object sender, RoutedEventArgs e)
{
Unloaded -= UserControl1_Unloaded;
Window window = Window.GetWindow(this);
window.PreviewKeyDown -= Window_PreviewKeyDown;
}
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.R && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.LeftCtrl)))
{
var viewModel = DataContext as YourViewModel;
if (viewModel != null)
viewModel.TakeCCommand.Execute();
}
}
}
Then the "hotkey" will work regardess of what element is currently focused in the window.
You may wrap this in an attached behaviour for reuse across several UserControls but how to this is another story that is not directly related to your actual question.

Related

Create a custom Dropdown button in WPF

Here is my firt question for Stackoverflow, I hope that will be ok!
I'm working on a custom Dropdown Button in WPF, and I would like to add a click event on the buttons "Text1" and "Text2". I have to put this dropdown button in a DLL so I use the WPF CustomControl library. So in the perfect world, I would like to create several methods in the MainWindow.xaml.cs and send the name of the method in a class where the name of the button, the icon , the tooltip, ... that will be used in the generic.xaml to find the method to call.
I hope what I said is clear :3
The purpose of this is to have a reusable dropdown button where I can add some click event in the items when we click on it.
Here is the generic.xaml with my dropdown button :
<Style TargetType="{x:Type local:ButtonDropdown}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ButtonDropdown}">
<mah:DropDownButton Content="{Binding Path=Text, RelativeSource={RelativeSource TemplatedParent}}"
ToolTip="{Binding Path=ToolTip, RelativeSource={RelativeSource TemplatedParent}}"
x:Name="DropDownButton"
Orientation="Vertical"
BorderThickness="0"
ItemsSource="{Binding ItemsSource}">
<mah:DropDownButton.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0" ToolTip="{Binding Tooltip}">
<StackPanel.InputBindings>
<MouseBinding Command="{Binding Path=SomeCommand, RelativeSource={RelativeSource TemplatedParent}}" MouseAction="LeftClick" />
</StackPanel.InputBindings>
<Image Source="{Binding Icon}" Width="16"></Image>
<TextBlock Text="{Binding Text}" x:Name="PART_DropdownButton">
</TextBlock>
</StackPanel>
</DataTemplate>
</mah:DropDownButton.ItemTemplate>
<mah:DropDownButton.Icon>
<Image Source="{Binding Path=Icon, RelativeSource={RelativeSource TemplatedParent}}" Width="32"></Image>
</mah:DropDownButton.Icon>
</mah:DropDownButton>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The call of this custom dropdown in the MainWindow.xaml :
<CustomButton:ButtonDropdown Text="Dropdown"
x:Name="ButtonDropdown"
Icon="Images/Open.png"
ToolTip="TOOLTIP DROPDOWN"
ItemsSource="{Binding Items}"/>
Here is my method OnApplyTemplate I add the line 'TextBlock textblock= GetTemplateChild("PART_DropdownButton") as TextBlock;' after the first answer.
public override void OnApplyTemplate()
{
DropDownButton dropDownButton = GetTemplateChild("DropDownButton") as DropDownButton;
TextBlock textblock= GetTemplateChild("PART_DropdownButton") as TextBlock;
textblock.MouseDown += Method1;
dropDownButton.ItemsSource = DropdownItems;
dropDownButton.Click += ButtonDropdown_Click;
}
And finally the class I have created for items in the dropdown :
public class DropdownItem
{
private string text;
private string icon;
private string tooltip;
private string clickEvent;
}
For the moment I have try with command and mousedown on textblock but don't work :/
Edit : I add the name for the textBlock and I add my method OnApplyTemplate from my ButtonDropdown.cs. The dropDownButton.click is ok but when I try to get the "PART_DropdownButton" that is null. I think because of there is not only one but several textBlock so he don't know which one to take. But that is my problem how to asign a different method on all textblock.mouseDown ? How can we put a different name on all textblock ?
Assuming your Dropdown Button derives from a button control give the DropDown button a name in the xaml file e.g. "PART_DropdownButton". Then reference the name in the code behind in the OnApplyTemplate procedure. Here you can add an event handler trapping your mouse events.
private DropdownButton dropdownbutton = null;
...
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
dropdownbutton = base.GetTemplateChild("PART_DropdownButton") as ToggleButton;
if (dropdownbutton != null)
{
dropdownbutton.MouseDown += MouseDown_Click;
}
else
....;
}
Next write your event handler for MouseDown_Click.
Regards Martin
I finally find something that works like I want !
I add an Icommand in my dropdownItem. That will contain my method.
public class DropdownItem
{
private string text;
private string icon;
private string tooltip;
private string clickEvent;
public ICommand ClickCommand { get; set; }
}
In my MainWindow.xaml.cs I add the command I need.
private ICommand _command1;
private ICommand _command2;
public MainWindow()
{
InitializeComponent();
Items.Add(new DropdownItem("Text1", "Images/Open.png", "Method1", "TEST")
{
ClickCommand = Command1
});
Items.Add(new DropdownItem("Text2", "Images/Open.png", "method2", "TEST2")
{
ClickCommand = Command2
});
ButtonDropdown.DropdownItems = Items;
}
public ICommand Command1
{
get
{
return _command1 = new RelayCommand(Method1);
}
}
public ICommand Command2
{
get
{
return _command2 = new RelayCommand(Method2);
}
}
public void Method1()
{
MessageBox.Show("Method 1");
}
public void Method2()
{
MessageBox.Show("Method 2");
}
And finally I add the call to this method in my generic.xaml
<MouseBinding Command="{Binding ClickCommand}" MouseAction="LeftClick" />
Thanks for your help, that's because of your comments and answers that I understood that I was looking in the bad direction

UWP Button click from ListViewItem

I really hope someone with more experience can give me a few pointers.
I have the following setup for a UWP project:
A ListView declared in XAML inside my application page, Tubes.xaml:
<ListView Name="TubesGrid"
ItemsSource="{x:Bind TubeItems, Mode=TwoWay}"
ItemTemplateSelector="{StaticResource TubeTemplateSelector}"
IsItemClickEnabled="True"
ItemClick="TubesGrid_ItemClick"
SelectionChanged="TubesGrid_SelectionChanged">
A UserControl as a template for the ListViewItem (UserControl.Resources):
<local:TubeTemplateSelector x:Key="TubeTemplateSelector"
TubeTemplate="{StaticResource TubeTemplate}">
</local:TubeTemplateSelector>
<DataTemplate x:Key="TubeTemplate" x:DataType="data:Tube">
<local:TubeTemplate HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FavoritesNumber="{x:Bind SpaceLength, Mode=OneWay}"></local:TubeTemplate>
</DataTemplate>
Inside the TubeTemplate I have a button, beside other views:
<Button Name="RemoveTube"
Click="RemoveTube_Click"
<Image
Source="../Assets/xIcon.png"
Stretch="None">
</Image>
</Button>
What I'm trying to achieve:
When I click the ListViewItem I want the ItemClick event to be triggered. This works.
But when I click on the Button that's inside the ListViewItem I want a different event to be triggered inside the main page.
The idea is to click on an item to select it, but when I click the button inside the item, I want that item to be removed.
What are my options?
If looks like are doing this without using viewmodels, then you could add an event to the TubeTemplate control.
public event EventHandler Closed;
When the close button is clicked, you would fire the event.
private void RemoveTube_Click(object sender, RoutedEventArgs e)
{
Closed?.Invoke(this, EventArgs.Empty); // Even better would be to give the item clicked (the data context)
}
Then, from within your MainPage you could subscribe to the event.
<local:TubeTemplate HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Closed="TubeTemplate_Closed">
</local:TubeTemplate>
In the TubeTemplate_Closed method, you could remove the item clicked.
private void TubeTemplate_Closed(object sender, EventArgs e)
{
var element = (FrameworkElement)sender;
var tube = (Tube)element.DataContext;
TubeItems.Remove(tube);
}
The idea is to click on an item to select it, but when I click the button inside the item, I want that item to be removed.
The better way is bind button command property with MainPage command method, and process the data source in the code behind. you could refer the following code.
Code Behind
public sealed partial class MainPage : Page
{
public MainPage()
{
MakeDataSource();
this.InitializeComponent();
DataContext = this;
}
public ObservableCollection<string> Items { get; set; }
private void MakeDataSource()
{
Items = new ObservableCollection<string>() { "Nico","CCor","Jack"};
}
public ICommand BtnCommand
{
get
{
return new CommadEventHandler<object>((s) => BtnClick(s));
}
}
private void BtnClick(object s)
{
Items.Remove(s as string);
}
}
public class CommadEventHandler<T> : ICommand
{
public event EventHandler CanExecuteChanged;
public Action<T> action;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this.action((T)parameter);
}
public CommadEventHandler(Action<T> action)
{
this.action = action;
}
}
Xaml code
Please note we need pass current focus listview item parameter to command method and remove it from data souce.
<Grid HorizontalAlignment="Stretch" x:Name="RootGrid">
<ListView ItemsSource="{Binding Items}" x:Name="MyListView">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
<Button HorizontalAlignment="Right"
Margin="0,0,30,0"
Content="Favorite"
Command="{Binding ElementName=MyListView,Path=DataContext.BtnCommand}"
CommandParameter="{Binding}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>

listview not refreshed when using databinding

I have a model class, Book, which contains a Keywords property:
public class Book : INotifyPropertyChanged
{
private ObservableCollection<string> _keywords;
...
public ObservableCollection<string> Keywords
{
get => _keywords;
set
{
_keywords = value;
OnPropertyChanged("Keywords");
}
}
}
and in my MainPage I have 2 components : a list View and a combobox whose each entry is a checkBox:
<ComboBox
x:Name="cbb_Keywords"
Grid.Column="2"
Width="300"
Margin="5,0,0,0"
HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource AllBooks}}"
DataContext="{Binding ElementName=listBoxBooks,Path=SelectedItem,UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Width="200" Content="{Binding}" Click="ButtonBase_OnClick">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource TextInListTrueFalseConverter}" Mode="OneWay">
<Binding ElementName="listBoxBooks" Path="SelectedItem.KeywordsForTextbox" Mode="OneWay"></Binding>
<Binding RelativeSource="{RelativeSource Self}" Path="Content"></Binding>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
the checkBox.IsChecked multibinding is oneway, and when I click on a checkbox, it calls this method:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
CheckBox cb = (CheckBox)sender;
var content = (string)cb.Content;
var keywords = ((Book)listBoxBooks.SelectedItem).Keywords;
bool clicked = cb.IsChecked.Value;
if (clicked)
keywords.Add(content);
else
keywords.Remove(content);
}
it works more or less but there are 2 caveats:
sometimes the checkbox on which I just clicked is displayed in the combobox's checkbox, which is not expected and is annoying
I have, in addition of the combobox, an other component, a textbox, which contains the list of the keywords for the listview's selectedItem:
but when I click on a checkbox to toogle this, the listbox containing the list is not refreshed...
so I chenged a little my Keywords property, in Book:
public ObservableCollection<string> Keywords
{
get => _keywords;
set
{
_keywords = value;
OnPropertyChanged("Keywords");
OnPropertyChanged("KeywordsForTextbox");
}
}
and the KeywordsForTextbox property is like this:
public string KeywordsForTextbox
{
get { return string.Join(",", _keywords); }
}
finally, to be complete, here is the textBox component in my MainWindow:
<TextBox x:Name="txb_Keywords"
Grid.Column="1"
Width="500"
Text="{Binding ElementName=listBoxBooks,Path=SelectedItem.KeywordsForTextbox,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}" />
why does the checkbox appears in the combobox's textbox? why isn't refreshed the other textbox?
thank you.
The problem is that when modifying the Keywords collection the actual Keywords property doesn't change. It's still the same collection object. Only the object's properties (Items) change.
In your Book class you could use methods to do the adding, and removing, then notify property changed from there.
public void AddKeyword(string name)
{
Keywords.Add(name);
OnPropertyChanged("Keywords");
}
public void RemoveKeyword(string name)
{
Keywords.Remove(name);
OnPropertyChanged("Keywords");
}
Then change your event like this.
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
CheckBox cb = (CheckBox)sender;
var content = (string)cb.Content;
var book = ((Book)listBoxBooks.SelectedItem);
bool clicked = cb.IsChecked.Value;
if (clicked)
book.AddKeyword(content);
else
book.RemoveKeyword(content);
}

WPF Binding Application Commands to ViewModel ICommand

Learning WPF with a small editor project and designing it with MVVM in mind.
The following code is throwing "Provide value on 'System.Windows.Data.Binding' threw an exception." at run time when the XAML is first parsed. No Build errors.
How best to bind my ICommands to Application Commands Close, Save, Save As, Open, New etc.
Currently I have just the Close and New setup.
XAML Code:
<Window x:Class="Editor.Views.EditorView"
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:Editor.Views"
xmlns:vm="clr-namespace:Editor.ViewModels"
xmlns:userControls="clr-namespace:Editor.UserControls"
mc:Ignorable="d"
Title="EditorView" Height="600" Width="800" WindowStartupLocation="CenterScreen">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:DocumentViewModel}">
<ContentControl Content="{Binding DocTextBox}" />
</DataTemplate>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close"
Executed="{Binding ExitCommand}" />
<CommandBinding Command="ApplicationCommands.New"
Executed="{Binding NewDocumentCommand}" />
<!--<CommandBinding Command="ApplicationCommands.Open"
Executed="OpenDocument" />
<CommandBinding Command="ApplicationCommands.Save"
CanExecute="SaveDocument_CanExecute"
Executed="SaveDocument" />
<CommandBinding Command="ApplicationCommands.SaveAs"
Executed="SaveDocumentAs" />-->
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Key="N" Modifiers="Control" Command="{Binding NewDocumentCommand}" />
<KeyBinding Key="F4" Modifiers="Control" Command="{Binding CloseDocumentCommand}" />
</Window.InputBindings>
<DockPanel>
<userControls:Menu x:Name="menu"
DockPanel.Dock="Top" />
<TabControl ItemsSource="{Binding Documents}" SelectedIndex="{Binding SelectedIndex}">
<TabControl.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding FileName}" />
<Button Command="{Binding CloseCommand}" Content="X" Margin="4,0,0,0" FontFamily="Courier New" Width="17" Height="17" VerticalContentAlignment="Center" />
</WrapPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</DockPanel>
</Window>
The ViewModel Code:
public class EditorViewModel : ViewModelBase
{
private static int _count = 0;
public EditorViewModel()
{
Documents = new ObservableCollection<DocumentViewModel>();
Documents.CollectionChanged += Documents_CollectionChanged;
}
#region Event Handlers
void Documents_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count != 0)
foreach (DocumentViewModel document in e.NewItems)
document.RequestClose += this.OnDocumentRequestClose;
if (e.OldItems != null && e.OldItems.Count != 0)
foreach (DocumentViewModel document in e.OldItems)
document.RequestClose -= this.OnDocumentRequestClose;
}
private void OnDocumentRequestClose(object sender, EventArgs e)
{
CloseDocument();
}
#endregion
#region Commands
private RelayCommand _exitCommand;
public ICommand ExitCommand
{
get { return _exitCommand ?? (_exitCommand = new RelayCommand(() => Application.Current.Shutdown())); }
}
private RelayCommand _newDocumentCommand;
public ICommand NewDocumentCommand
{
get { return _newDocumentCommand ?? (_newDocumentCommand = new RelayCommand(NewDocument)); }
}
private void NewDocument()
{
_count++;
var document = new DocumentViewModel { FileName = "New " + _count, DocTextBox = new RichTextBox() };
Documents.Add(document);
SelectedIndex = Documents.IndexOf(document);
}
private RelayCommand _closeDocumentCommand;
public ICommand CloseDocumentCommand
{
get { return _closeDocumentCommand ?? (_closeDocumentCommand = new RelayCommand(CloseDocument, param => Documents.Count > 0)); }
}
private void CloseDocument()
{
Documents.RemoveAt(SelectedIndex);
SelectedIndex = 0;
}
#endregion
#region Public Members
public ObservableCollection<DocumentViewModel> Documents { get; set; }
private int _selectedIndex = 0;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
_selectedIndex = value;
OnPropertyChanged();
}
}
#endregion
}
When you are using CommandBinding, arguably you are configuring commands that the view should be handling. As such, it's not clear to me that it makes sense to implement the command in the view model. Conversely, if the view model should own the command, then use its command, not a pre-defined one.
It doesn't make sense to ask to bind your ICommand object to an application command. The ApplicationCommands objects are themselves ICommand implementations! (RoutedUICommand, to be specific.)
If your view model already implements ICommand for the standard commands, then just bind to those:
<CommandBinding Command="{Binding ExitCommand}"/>
If you really want to use the ApplicationCommands commands, then you'll need to subscribe an event handler method to the Executed and CanExecute events and then delegate those to the view model. For example:
<CommandBinding Command="ApplicationCommands.Close"
Executed="Close_Executed" />
Then in code-behind, something like this:
void Close_Executed(object sender, ExecutedRoutedEventArgs e)
{
ICommand command = (ICommand)e.Parameter;
command.Execute(null);
}
Note that you'd have to make sure in this case that you set the CommandParameter at the source of the command itself. I.e. include CommandParameter={Binding ExitCommand} in the InputBinding and Button where you invoke the command. This could get tedious.
Alternatively, you could assume that the DataContext of the Source object is your view model and get the command directly from that:
void Close_Executed(object sender, ExecutedRoutedEventArgs e)
{
EditorViewModel viewModel = (EditorViewModel)((FrameworkElement)e.Source).DataContext;
ICommand command = viewModel.ExitCommand;
command.Execute(e.Parameter);
}

View, ViewModel and DataContext

In order to solve a navigation issue in my application I have used an Event Aggregator which has solved the problem but has created an other one.
To navigate between different UserControls I used the Rachel's code you can find here which was working fine until I made some changes.
On the side of my screen I have a Main Menu (HomeViewModel()), by clicking on the items I switch between UserControls and in each of these UserControls there is a another menu bar where I can switch between other UserControls.
But this second menu (CateringMenuViewModel()) doesn't work anymore. The UserControl is displayed but nothing is happening when I am clicking in the menu bar.
At the first sight I thought it's because there is no DataContext.
So I added it in the code behind like this:
public CateringMenuView()
{
InitializeComponent();
this.DataContext = new CateringMenuViewModel(ApplicationService.Instance.EventAggregator);
}
But it still doesn't work.
I don't understand, the property Name is well bounded because the names are displayed in the menu but the command ChangePageCommand is not.
HomeViewModel
public class HomeViewModel : ObservableObject
{
#region Fields
private ICommand _changePageCommand;
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
#endregion
public HomeViewModel()
{
// Add available pages
PageViewModels.Add(new HomeOrderViewModel());
PageViewModels.Add(new CateringMenuViewModel(ApplicationService.Instance.EventAggregator));
PageViewModels.Add(new HomeAdminViewModel());
// Set starting page
CurrentPageViewModel = PageViewModels[0];
}
#region Properties / Commands
}
CateringMenuViewModel
public class CateringMenuViewModel : ObservableObject, IPageViewModel
{
protected readonly IEventAggregator _eventAggregator;
public CateringMenuViewModel(IEventAggregator eventAggregator)
{
this._eventAggregator = eventAggregator;
PageViewModels.Add(new NewRegularOrderViewModel(ApplicationService.Instance.EventAggregator));
PageViewModels.Add(new NewDeliveryComOrderViewModel());
PageViewModels2.Add(new FillOrderViewModel());
// Set starting page
CurrentUserControl = PageViewModels[0];
this._eventAggregator.GetEvent<GoToFillOrder>().Subscribe(GoToFillOrder);
}
public string Name
{
get
{
return "Catering";
}
}
public string imageSource
{
get
{
return "catering.ico";
}
}
#region Fields
private List<IUserContentViewModel> _pageViewModels;
public List<IUserContentViewModel> PageViewModels
{
get
{
if (_pageViewModels == null)
_pageViewModels = new List<IUserContentViewModel>();
return _pageViewModels;
}
}
private IUserContentViewModel _currentUserControl;
public IUserContentViewModel CurrentUserControl
{
get { return _currentUserControl; }
set
{
if (value != _currentUserControl)
{
_currentUserControl = value;
OnPropertyChanged("CurrentUserControl");
}
}
}
#region Methods
private void ChangeViewModel(IUserContentViewModel viewModel)
{
if (!PageViewModels.Contains(viewModel))
PageViewModels.Add(viewModel);
CurrentUserControl = PageViewModels
.FirstOrDefault(vm => vm == viewModel);
var x = this.GetHashCode();
}
#endregion
private ICommand _changePageCommand;
#endregion
public ICommand ChangePageCommand
{
get
{
if (_changePageCommand == null)
{
_changePageCommand = new RelayCommand(
p => ChangeViewModel((IUserContentViewModel)p),
p => p is IUserContentViewModel);
}
return _changePageCommand;
}
}
private void GoToFillOrder(int i)
{
CurrentUserControl = PageViewModels2[0];
}
}
CateringMenuView
<UserControl.Resources>
<DataTemplate DataType="{x:Type cvm:NewDeliveryComOrderViewModel}">
<cv:NewDeliveryComOrderView/>
</DataTemplate>
<DataTemplate DataType="{x:Type cvm:NewRegularOrderViewModel}">
<cv:NewRegularOrderView/>
</DataTemplate>
<DataTemplate DataType="{x:Type cvm:FillOrderViewModel}">
<cv:FillOrderView/>
</DataTemplate>
</UserControl.Resources>
<Grid Margin="5">
<Grid>
<StackPanel>
<Menu>
<MenuItem Header="New Order">
<ItemsControl ItemsSource="{Binding PageViewModels}" Width="168" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Hyperlink Command="{Binding ChangePageCommand, Mode=OneWay}" CommandParameter="{Binding}" TextDecorations="{x:Null}">
<InlineUIContainer>
<TextBlock Text="{Binding Name}"/>
</InlineUIContainer>
</Hyperlink>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</MenuItem>
</Menu>
</StackPanel>
</Grid>
<ContentControl Content="{Binding CurrentUserControl}"/>
</Grid>
Two problems here.
First off, you do not want to set the .DataContext of your UserControl manually because you want to use the CateringMenuViewModel from PageViewModels[1], not create a new instance of it.
So definitely remove the line of code
DataContext = new CateringMenuViewModel(ApplicationService.Instance.EventAggregator);
Second problem is why your event is not firing. I took a look at your code in your question's version history, and I do not see you broadcasting the event anywhere.
This line of code is correct to say "any time an event of type GoToFillOrder is broadcast, run the method GoToFillOrder"
_eventAggregator.GetEvent<GoToFillOrder>().Subscribe(GoToFillOrder);
however I don't see any code which actually broadcasts that event. You need a line of code like the following to broadcast the GoToFillOrder message to throughout your application :
_eventAggregator.GetEvent<GoToFillOrder>().Publish();
I finally found the solution.
In CateringMenuView(), I have replaced
<Hyperlink Command="{Binding ChangePageCommand, Mode=OneWay}"
CommandParameter="{Binding}"
TextDecorations="{x:Null}">
by
<Hyperlink Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}"
TextDecorations="{x:Null}">
Big thanks to Rachel!

Categories