Pass Element as CommandParameter from root of ElementTree - c#

I searched a lot through Google and StackOverflow, but nothing answered my problem.
I have two Xaml Files:
MainWindow.xaml
<Window x:Name="mainWindow">
<Window.DataContext>
<!-- Instantiate ViewModel of the MainWindow -->
<vm:MainWindowViewModel x:Name="viewModel"/>
</Window.DataContext>
<!-- Create the Menu of the MainWindow -->
<custom:MainMenu Grid.Row="0"/>
<ad:DockingManager x:Name="dockingManager">
<!-- ... -->
</Window>
And the MainMenu.xaml
<UserControl>
<Menu>
<MenuItem Header="{t:Translate MENU_LAYOUT_SAVE}" Command="{Binding SaveLayoutCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"/>
<MenuItem Header="{t:Translate MENU_LAYOUT_LOAD}" Command="{Binding LoadLayoutCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"/>
</Menu>
</UserControl>
And here my Problem occurs. Instead of passing the Mainwindow-object I want to pass the DockingManager x:Name="dockingManager" from MainWindow. But if i try to reference the object by its name it fails...
I tried the following Bindings:
CommandParameter="{Binding ElementName=dockingManager}"
CommandParameter="{Binding ElementName=dockingManager, RelativeSource={RelativeSource AncestorType=Window}}"
So how can I find and reference an Object (dockingManager) from the ElementTree within xaml. I want to avoid using extra code in Code-behind.

Try CommandParameter="{Binding ElementName=dockingManager, Path=.}".
EDIT:
The previous answer would not work. Here's a working idea...
In the Window.xaml:
<custom:MainMenu Grid.Row="0" Tag="{Binding ElementName=dockingManager}" />
In the MainMenu.xaml:
<UserControl x:Name="UcMainMenu" />
...
<MenuItem Header="{t:Translate MENU_LAYOUT_SAVE}" Command="{Binding SaveLayoutCommand}" CommandParameter="{Binding ElementName=UcMainMenu, Path=Tag}"/>

You can use:
CommandParameter="{x:Reference Name=yourElementName}"

Since you are using MVVM here is what you should do to come up with a slightly different solution:
Get rid of the CommandParameter
The command will trigger a callback in the MainWindowViewModel instance
This callback will change some state/properties in the MainWindowViewModel instance
The DockingManager instance reacts to that adjusted state of the MainWindowViewModel instance through bindings
The way you are doing it now is way too complicated. In addition to that, you are wildly mixing patterns here. MVVM tries to separate the business logic from the actual elements. You are using elements of MVVM with Smart UI/Code Behind techniques.
Also, consider using individual view models for individual controls. The main menu control is separate and the docking manager is, too. Why? Because you want to break everything into smaller pieces, but more importantly, because you might have reusability in mind. With the main menu trying to access a docking manager inside a Window that is not possible.

Related

Context menu binding never gets executed

I have ListView control in my view with it's own viewmodel A. I have made a seperate UserControl to use as ListViewItem, because it's styling takes a lot of space. In this ListViewItem I have a button, which is binded to viewmodel A and it works fine.
As the context menu has it's own visual tree and cannot bind via ancestor, I have used binding proxy, to solve this issue. I have tweaked it a little so it worked for my particular case, because if it just used {Binding} it would bind to item's model, not listview's viewmodel.
<helpers:BindingProxy x:Key="proxy" Data="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListView}}"/>
To check if the binding is correct I've used a converter as a way just to have a breakpoint to check source. Everything was good and I was getting my viewmodel right there.
Now, when I try to bind to this in my context menu
<UserControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Open"
Command="{Binding DataContext.OpenChatCommand, Source={StaticResource proxy}, Converter={StaticResource DataBindingDebugConverter}}"
CommandParameter="{Binding}"/>
</ContextMenu>
</UserControl.ContextMenu>
The command never gets called. I added converter to see if something is wrong, but it turns out, I never get to my converter, which in turn means this code never gets executed.
Anyone with any ideas why this is happening and how to solve this is welcome.
I think it's the compiler malfunctioning though
I just did a brief readup on that "binding proxy" you mentioned, but as far as I know, DataGridTextColumn is in the same Visual Tree as its DataGrid, just that its DataContext is bound to its data.
For ContextMenu, it's totally different. This one really has a separate tree from its parent. There is no point in using a proxy object in resources, because it is from a different visual tree. When you use StaticResource, WPF will search upwards through its visual tree, level by level, inside those elements' Resource property (which is a ResourceDictionary).
One way is to make that proxy into a singleton, and use Source={x:Static helpers:BindingProxy.Instance}. Of course using this means that your proxy can only be used by a single View, or else something unexpected would happen.
The other way is to make use of PlacementTarget property of the ContextMenu.
<ContextMenu DataContext="{Binding Path=PlacementTarget.DataContext,
RelativeSource={RelativeSource Self}}">
This is the preferred way, but you need to make sure the parent's DataContext is really the VM that you need.
Edit
There is no super elegant way to do it the MVVM way. The best way is probably through the use of Tag property.
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag,
RelativeSource={RelativeSource Self}}">
ListView Control:
<MyControl:MyListViewItem .... Tag="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type MyControl:MyListViewView}}}"}" ...>

WPF MVVM Navigation Technique

I know there are a lot of questions about WPF navigation, for application developed with MVVM pattern, and I have read tens and tens of answers but I'm missing probably something.
I started building an application following Rachel's article here. All works just fine, there's an ApplicationView Window with this XAML:
<Window x:Class="CashFlow.ApplicationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:CashFlow.ViewModels"
xmlns:v="clr-namespace:CashFlow.Views"
Title="ApplicationView" Height="350" Width="600" WindowStartupLocation="CenterScreen">
<Window.Resources>
<!--Here the associations between ViewModels and Views-->
<DataTemplate DataType="{x:Type vm:HomeViewModel}">
<v:HomeView />
</DataTemplate>
</Window.Resources>
<!--Define here the application UI structure-->
<DockPanel>
<Border DockPanel.Dock="Left" BorderBrush="Black" BorderThickness="0,0,1,0">
<ItemsControl ItemsSource="{Binding PageViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding}"
Margin="2,5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<ContentControl Content="{Binding CurrentPageViewModel}" />
</DockPanel>
The ApplicationViewModel, that is set as DataContext for this window when the application starts, maintains an ObservableCollection of my ViewModels. Thanks to data templates, it's possible to associate every view with its viewmodel, using a ContentControl to render the views. Navigation in this case is accomplished with a "side bar" of buttons, binded to ApplicationViewModel commands that perform the changes of CurrentPageViewModel object.
I'm wondering how I can perform navigation without the presence of that sidebar of Buttons. Having only the Content control, I should be able to change the CurrentPageViewModel from the others viewmodel? Probably the answer will be very trivial, but I can't see that right now.
Your top level homeviewmodel can orchestrate navigation via an eventbus pattern. To use eventbus, you would inject an object that tracks objects that want to be notified of events. Then when a view model raises an event, the homeviewmodel receives it and performs the currentpageviewmodel assignment that will navigate you to the next viewmodel.
Ex:
Messenger defines two methods - RegisterForEvent<IEvent>(ViewModel aViewModel), and RaiseEvent(IEvent event).
So you would define a function to subscribe to the events -
HomeViewModel.cs
...
void SubscribeForEvents() {
Messenger.RegisterForEvent<NavigationEvent>(this);
}
Then you inject the Messenger into your other view models, and from those view models, raise the event:
Messenger.RaiseEvent(new NavigationEvent { TargetViewModel = new TargetViewModel() });
Where the event is something like
public class NavigationEvent : IEvent {
ViewModel TargetViewModel { get;set;}
}
C Bauer is right with what you are missing. I found in order to switch the data context, you'll need a messenger service to flag your "applicationviewmodel" to switch its data context. A good discussion with the steps you need are spelled out in a discussion here.
Register the message to be received in your applicationviewmodel, then handle the data context switch in your receive message function.
Also, this might be true or not, but I had to use 1 window, with multiple user controls as opposed to multiple windows if I wanted to have 1 window showing at all times. Lastly, I followed Sheridan's example and defined my data templates in my app.xaml as opposed to the window itself.

pass the window using a CommandParameter from a ContextMenu

The goal is to hide/show a window from the task tray using Hardcoded WPF NotifyTrayIcon in a MVVM solution. The problem is the CommandParameter always seems to be null, which then of course cause the code to crash. I've tried a number of different bindings including:
CommandParameter="{Binding Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}"
CommandParameter="{Binding ElementName=window, Mode=OneWay}"
etc but everything passes back a null, any suggestions?
Thanks
If you have only one Window then you could simply use Application.Current.MainWindow.
If you have more then you may search for the good one in the Application.Current.Windows collection.

How to add UserControl to Canvas of MainView from multiple ViewModels in WPF MVVM

I have a MainWindowView(Window) with a Canvas in which I add my Views(UserControls).
The Canvas in the MainWindow is a Custom Canvas derived from Canvas so that Views inside this can be moved here and there, and can bringtofront or sendback.
I add Views to MainWindowView's Canvas by Binding a Command to a Button. So when I click a Button, a View gets added in the Canvas.
But, my problem is, I want to add another View to the same Canvas of MainViewModel from the ViewModel of my Views which are already in the Canvas of MainViewModel.
Since the ObservableCollection, which I used to bind Canvas, is in MainViewModel, I can add View from the MainViewModel only.
When I try to use the MainViewModel from other ViewModel, I have to create a new object of it, which makes the old View in the Canvas being replaced by the new one.
Is there a solution for this. If not what's the use of using MVVM framework.
Please help...
Use Calibrum Micro, which will help you in this
Am I getting this right : Your controls' DataContext is a Different one than that of the Window and you need to access it from there?
Basically that could have been avoided by design (use Dependency Injection to get the MainViewModel instance into the Command), but in fact there is a WPF/MVVM friendly way of solving this:
Use Commands to Add Controls to the MainViewModels ObservableCollection
<Button Command="{Binding Path=CreateViewCommand}" CommandParameter="{Binding}" />
From your Control (what you called View), you must use Ancestor Binding:
<Button Command="{Binding Path=DataContext.CreateViewCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
CommandParameter="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
In your Command, you can cast the parameter to its original Type (MainViewModel) and work with it as you wish.

How would you know which element of an ItemsControl sends an event in MVVM?

Let's say I currently have an ItemsControl whose DataTemplate is a bunch of buttons. I'm wiring up these buttons' click events, but how am I to know which button was clicked? Should I not use a ItemsControl?
I'm trying to have no code-behind, but being pragmatic may be necessary.
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="10">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding ItemsControlButtonClicked, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you want to know what Item was clicked, then pass {Binding } as the CommandParameter and it will pass the selected object to your Command
If you want to know what Button was clicked, I would do that in the code-behind since ViewModels do not need to know anything about the UI, and that includes buttons.
Also since your control is a Button, you should use the Command property instead of a Click trigger.
<Button Command="{Binding ItemsControlButtonClicked}" />
You can send parameters along with the command and based on these parameters you can find out which button was clicked
In my project I also use the MVVM Light I has an dropdown with collection of items, and a button which user press and action depend on selected item from drop down
you should create a Relay command with parameter look at the example from my code
public RelayCommand<Project> StartTimer { get; private set; }//declare command
StartTimer = new RelayCommand<Project>(OnStartTimer);
private void OnStartTimer(Project project)
{
if (project != null)
{
currentProject = project;
if (!timer.IsTimerStopped)
{
timer.StopTimer();
}
else
{
Caption = "Stop";
timer.StartTimer();
}
}
on the view I bind the drop down with collection of class Project
and for button command parameter I bind the selected item form drop down
look at the code
<ComboBox Name="projectcomboBox" ItemsSource="{Binding Path=Projects}" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="FullName"
SelectedValuePath="Name" SelectedIndex="0" >
</ComboBox>
<Button Name="timerButton" Content="{Binding Path=Caption}" Command="{Binding Path=StartTimer}"
CommandParameter="{Binding ElementName=projectcomboBox, Path=SelectedItem}" ></Button>
pay attention to Command and CommandParameter binding
also you can use this approache not only for drop down
Well, you can use the Sender.DataContext which is the actual data.
Create command properties in your view model class (using Josh Smith's RelayCommand pattern is the simplest way to do this) and bind each button's Command to the appropriate one. Not only is this straightforward to do and simple to maintain, it also gives you an easy way of implementing the enable/disable behavior when you need to.

Categories

Resources