"Bubbling" events from bound viewmodel goes to parent - c#

First off, I'm still getting used to Caliburn and WPF, so I may be doing this the wrong way. I'd love to know a better way!
I have a Conductor shell view model, which has a view with some shared elements, and a content control for the ActiveItem. Now, I want a set of buttons from the ActiveItem view model to be displayed outside the content control, but still have their click events go to the active view model. I've tried adding an ItemsControl like this:
<ItemsControl x:Name="ActiveItem_Buttons">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}" Margin="10" Height="25" Width="80">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="{Binding Name}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
(I'm doing the Interaction.Trigger part since I cannot bind to x:Name which would cause Caliburn to wire it automagically)
However, I get an exception stating that Caliburn cannot find a matching method. It seems like it starts bubbling the event from the shell view model, instead of the active item view model. How can I set the "starting point" of the event bubbling? I found and tried the attached property cal:View.Model="{Binding ActiveItem}", but that did not seem to help.
EDIT: For clarity: the Buttons property is defined on the view model base class ButtonScreenBase, and the shell inherits Conductor<ButtonScreenBase>

Rather don't use events and event handling in WPF, especially in conjunction with MVVM. Use the Command pattern instead. For user actions this means you can trigger Commands via gestures, clicks and other kind of user interaction events in the View and contain the business logic in the Command class/methods which is triggered in the ViewModel from the View, thus maintaining the separation of user interface logic and business logic.
I recommend this link for learning about Commands and how you can implement them (and MVVM from base principles) : http://kentb.blogspot.co.uk/2009/04/mvvm-infrastructure-delegatecommand.html

update: went back to check some other posts about this particular problem, an explanation about the problem from the man himself.
Bind a Command to a Button inside a ListView with Caliburn.Micro
<Button Content="{Binding Name}" cal:Bind="{Binding}" Margin="10" Height="25" Width="80">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="{Binding Name}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
same thing it's an inherit issue with datatemplating.

Related

Invoking Commands from ListView in MVVM

I'm using Prism for Windows Runtime to wire up events in my Views with DelegateCommands in my ViewModels. I am wondering what would be the best way to invoke commands (e.g. select item) from a ListView that contains Buttons (or custom controls derived freom the Button class). I'd like to keep the effects (e.g. background change, tilt effect) provided by the Button control. But the button unfortunately absorbs the click events, which, in consequence, I cannot use in the ListView to hook up my commands e.g with the following XAML (and Behaviors SDK):
<ListView ItemsSource="{Binding AvailableItemsList}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<customControls:NavMenuButton Style="{StaticResource SelectionListMenuButton}" Content="{Binding Nickname}" DescriptionText="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SelectionChanged">
<core:InvokeCommandAction Command="{Binding ItemSelectedCommand}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</ListView>
What would be the best way to achieve this? I have found similar questions, but the difference here is that the controls in the list items are apparently "stealing" the click event (while it works just fine with e.g. a simple TextBlock).
To close this question, here is the solution based on MatDev8's comment above (thank you!):
<ListView x:Name="myListView" ItemsSource="{Binding AvailableItemsList}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<customControls:CustomTextButton Style="{StaticResource SelectionListMenuButton}"
Content="{Binding Nickname}"
DescriptionText="{Binding Name}"
Command="{Binding DataContext.ItemSelectedCommand, ElementName=myListView}"
CommandParameter="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
To have the commands binding in ListView you can use buttons within the ListView.
now for the clicking related issue you can modify your button's Controltemplate to make it look like a simple textblock. this way your clicking will also work on the listview and it will not be displayed as button.
<ControlTemplate TargetType="Button">
<TextBlock Text="{TemplateBinding Content}" />
</ControlTemplate>
You can also customize the click event in other way like moving focus to the button should also raise your click event(indirectly fire your command). This will help in your case of Listview where just moving to next item should also fire the command.

How to Interact with 2 or More ViewModels

I have written a tool in which a ListBox is bound to a ObserservableCollection<object> with varying datatypes I've define. I use a PropertyDataTemplateSelector to present the data in the ListBox. The PropertyDataTemplateSelector references several DataTemplates that are set as UserControls. There is a background class that provides logic to the PropertyDataTemplateSelector by checking the object type and then applying the correct DataTemplate.
Here's an abbreviated example of the XAML for the UserControls and the MainWindow.
UserControl1
<TextBlock Text="{Binding Path=Val1}"
Style="{StaticResourse Yes}" />
<Button Content="I'm Button 1"
Command="{Binding Path=PathtoCommand1}"
CommandParameter="{Binding Parameter1}"
IsEnabled="{Binding IsEnabled1}" />
<Button Content="I'm Button 2"
Command="{Binding Path=PathtoCommand2}"
CommandParameter="{Binding Parameter2}"
IsEnabled="{Binding IsEnabled2}"
Tag="{Binding Path="DataContext.TagItem2}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem IsCheckable="True"
IsChecked="{Binding Path=Tag}"
DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
</UserControl>
UserControlN
<UserControl x:Class="AwesomerControl">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=FancyName2}"
Style="{StaticResourse Yes}" />
<Button Content="Clicker 1"
Command="{Binding Path=DoSomethingGreat1}"
CommandParameter="{Binding Greatness1}"
IsEnabled="{Binding IsTurnedOn1}" />
<Button Content="Clicker 2"
Command="{Binding Path=DoSomethingGreat2}"
CommandParameter="{Binding Greaterness2}"
IsEnabled="{Binding IsTurnedOn2}"
Tag="{Binding Path="DataContext.TagItem2}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem IsCheckable="True"
IsChecked="{Binding Path=Tag}"
DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
</UserControl>
Here I set the UserControls to a specified DataTemplate. The UserControls were moved out to make the XAML easier to read/navigate. In actuality the UserControls are a few hundred lines each.
<Window.Resources>
<DataTemplate x:Key"Template1">
<customControls:AwesomeControl/>
</DataTemplate>
...
<DataTemplate x:Key"TemplateN">
<customControls:AwesomerControl/>
</DataTemplate>
<dts:PropertyDataTemplateSelector x:Key="templateselector"
Template1="{StaticResource Template1"}
...
TemplateN="{StaticResource TemplateN"}
</Window.Resources>
The ListBox is defined as this.
<ListBox ItemSource="{Binding Path=CollectionofMyObjects}"
ItemTemplateSelector="{StaticResource templateselector}" />
I am using a single ViewModel to drive the MainWindow and the UserControls.
So that's where I'm at, essentially. I have this currently working as I'd like, but in an ongoing effort to learn (this is my first MVVM/WPF/C# project) I'd like to keep exploring how to make my code "better" (however that's defined). I'm not looking to solve an error here. So to avoid a general/broad question, I'll ask what I think I want to know. Someone can correct me and I'll update the "question(s)" appropriately
Question: How can I go about producing a ViewModel for each of the UserControls? Some of the ViewModels, for the UserControls, will occasionally require two-way communication to the MainWindow_ViewModel. The main crux of my problem is figuring out how the multiple VMs will communicate.
You're close, but it's not quite MVVM yet. ;)
First, break out all the functionality that is relevant to each UserControl into their own classes. These are your view-model classes.
Your controls should now become "view" classes, and they deserve their own mark-up file. Rather than use a template selector, you can use the DataTemplate.DataType to automatically connect the view-model class type to its view.
There are a lot of options for communication between view-models. To further your education, I'd consider looking at a light-weight MVVM framework that has built-in solutions for communication. My personal favorite is Caliburn.Micro, which includes an EventAggregator, a service that provides the ability to publish an object from one view-model to another in a loosely-coupled fashion.
Keep learning, you're on the right track!

Disabled menu element in WPF looks enabled

My problem is twofold, but i guess that they are related, and if I manage to fix one of them, I will solve both.
First of, lets see the xaml code for a ContextMenu that is linked to a Caliburn.Micro view model:
<ContextMenu>
<MenuItem Header="Configure modem" ItemsSource="{Binding Modems}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ca:ActionMessage MethodName="SelectModem">
<ca:Parameter Value="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
</ca:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
<MenuItem.Header>
<DockPanel>
<Image DockPanel.Dock="Left" Source="{Binding CarrierProfile.CarrierProfileIcon}" Width="40" Height="40"/>
<TextBlock Text="{Binding MenuText}" VerticalAlignment="Center" Margin="10 0"/>
</DockPanel>
</MenuItem.Header>
</MenuItem>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu>
So basically this is just a DataTemplate where I set the Header to a DockPanel containing an image and a TextBlock.
One MenuItem looks like this:
Here you can see the main problem. You can see that there are "two selections". One outer selection, and one inner. If I click the inner selection, everything is fine, and my SelectModem method is called from my view model. However, if you click the outer selection the context menu goes away so that user thinks he has made a selection, but actually no method is called on the view model.
My second problem is that if I disable the MenuItem by adding IsEnabled="False" in the code above, the menu item looks disabled (text is grayed out), I cannot make the inner selection, but on hover is still shows the outer selection, and when clicked the menu goes away (but nothing is triggered in my view model)
So the question is: How can I get rid of the the outer selection?

Command binding inside a DataGridTemplateColumn

I am working on a Silverlight application which makes an extensive use of Prism, the MVVM pattern and MEF. For several reasons, I have chosen to follow a View-first approach.
In one of the Views there is a DataGrid, and one of the columns of this grid is a DataGridTemplateColumn, which has just a Button.
I'd like to define both a Command and a CommandParameter on the Button. The Command should be a DelegateCommand of the ViewModel. CommandParameter should be the SelectedItems list coming straight out of the dataGrid.
I've tried several approaches to do this, but either Command or CommandParameter are null.
It follows the code that I originally wrote:
<sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Width="15" Height="15" Content=">"
Command="{Binding UpdateSearchParametersCommand}"
CommandParameter="{Binding SelectedItems, ElementName=dataGrid}">
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
Could someone advice me on what the best way to go about it is?
Thanks in advance,
Gianluca.
Your current binding is pointing to DataGridRowItem.UpdateSearchParametersCommand. You need to change it to point to DataGrid.DataContext.UpdateSearchParametersCommand
<sdk:DataGrid x:Name=dataGrid>
<sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Width="15" Height="15" Content=">"
Command="{Binding DataContext.UpdateSearchParametersCommand, ElementName=dataGrid}"
CommandParameter="{Binding SelectedItems, ElementName=dataGrid}">
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid>
If you bind your DataGrid using ItemsSource, then Command and CommandParameter binding is associated to the current item - the way you've written.
You should use alternative source in this case. Command should be binded to the DataContext.UpdateSearchParametersCommand and CommandParameter - to DataContext.SelectedItems.
In your case neither UpdateSearchParametersCommand, nor SelectedItems cannot be found in the binded item.
UPDATED
Be sure to set the right type for ancestor. I've set it to window, but maybe you are using UserControl.
<sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Width="15" Height="15" Content=">"
Command="{Binding Path=DataContext.UpdateSearchParametersCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
CommandParameter="{Binding Path=DataContext.SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
In silverlight 5 you can do this
<Button Command="{Binding Path=DataContext.PreviewPublishCommand, RelativeSource={RelativeSource AncestorType=controls:ChildWindow}}" Content="Publish" />
Just adjust AncestorType to be whatever your top level element is (UserControl, ChildWindow, etc).
Many of you tried to help me out on this. Thank you for that.
Unfortunately the provided answers were mostly relative to WPF.
Here is how I've solved the problem:
<helpers:BindingHelper.Binding>
<helpers:BindingList>
<helpers:RelativeSourceBinding TargetProperty="Command" Path="DataContext.ToggleDataArchiveInheritanceCommand" RelativeMode="FindAncestor" AncestorType="ChildWindow" />
</helpers:BindingList>
</helpers:BindingHelper.Binding>
Ok, this comes from another point of the same application, but the principle is the same.
If a binding is defined inside a , the only way you have in Silverlight to reach out other elements that normally would be out-of-scope (as they are not part of the DataTemplate) is to walk through the xaml object tree. That's what the BindingHelper does.
Posting here as I hope the information will be useful to someone else.
Cheers,
Gianluca

WPF: Accessing two DataContexts in the same control

I am using an MVVM approach, and I have an object from my ViewModel called DatabasesSubFrame which is DataTemplated to show a ListBox. I want to display a Button below the ListBox, which binds to both the currently SelectedItem, and a property on the DatabasesSubFrame object which is being DataTemplated.
I know how to refer to the currently selected item, by setting the DataContext on a shared ancestor with the ListBox and use {Binding /}. In this example the shared ancestor is a StackPanel. And if the DataContext wasn't explicitly set there I could easily bind to a property on the DatabasesSubFrame object by just doing {Binding SomeProperty}. However, if I do {Binding SomeProperty} within the explicitly set DataContext, it refers to the wrong DataContext.
How do I access the "original" DataContext here? I tried messing with RelativeSources and TemplatedParents but couldn't figure out how to fit them in.
<DataTemplate DataType="{x:Type VM:DatabasesSubFrame}">
<StackPanel DataContext="{Binding Databases}" >
<ListBox Name="DbInfoBox"
ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding ShortName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- Problem: The Command and V:CreateCommandBinding.Command are set incorrectly here. How do I access OpenDbCommand from the top-level DataTemplate's DataContext? -->
<Button Content="Open Database"
CommandParameter="{Binding /}"
Command="{Binding ???, Path=OpenDbCommand.Command}"
V:CreateCommandBinding.Command="{Binding ???, Path=DataContext.OpenDbCommand}"/>
</StackPanel>
</DataTemplate>
I think this question will help you to find the answer to yours. Another trick is to set the Name of the Window to something like "Root". You can then get at the window's original datacontext by using:
{Binding ElementName=Root, Path=DataContext.MyViewModelsProperty}

Categories