Binding Command to loaded viewmodel - wpf

I have a section in my MainMenuView that loads a selected viewmodel in a ContentControl. The DataContext of the MainMenuView is MainMenuViewModel.
Can I bind a button from the main menu to a command at the SelectedViewModel or do I have to raise an event?
<Fluent:Ribbon>
<!--Userdata-->
<Fluent:RibbonTabItem Header="Data" x:Name="TabVerm" Group="{Binding ElementName=VermittlerGroup}">
<Fluent:RibbonGroupBox Header="Data">
<Fluent:Button" Command="{Binding Path=DataContext.VermittlerSave, ElementName=VermittlerView}" Header="Save" LargeIcon="{iconPacks:FontAwesome Kind=SaveRegular,Width=30,Height=25}"></Fluent:Button>
<Fluent:Button Header="Cancel" LargeIcon="{iconPacks:FontAwesome Kind=UndoAltSolid,Width=30,Height=25}"></Fluent:Button>
</Fluent:RibbonGroupBox>
<Fluent:RibbonGroupBox Header="Activate">
<Fluent:Button Header="Aktivate User" LargeIcon="{iconPacks:FontAwesome Kind=StackExchangeBrands,Width=30,Height=25}"></Fluent:Button>
<Fluent:Button Header="New User" LargeIcon="{iconPacks:FontAwesome Kind=PlusCircleSolid,Width=30,Height=25}"></Fluent:Button>
</Fluent:RibbonGroupBox>
</Fluent:RibbonTabItem>
</Fluent:Ribbon>
<Grid Grid.Row="1">
<ContentControl Grid.Row="1" Content="{Binding SelectedViewModel}"/>
</Grid>

You should be able to do this by giving your ContentControl a name.
<ContentControl Grid.Row="1" x:Name="MyContentControl" Content="{Binding SelectedViewModel}"/>
Then you can refer to it and its Content property that holds the view model in the command binding, e.g.:
<Fluent:Button" Command="{Binding Content.VermittlerSave, ElementName=MyContentControl}" Header="Save" LargeIcon="{iconPacks:FontAwesome Kind=SaveRegular,Width=30,Height=25}"/>
Of course, this is only applicable, if any selected view model contains the bound command.
Alternatives to binding are communicating with events, e.g. using the event aggregator pattern that most MVVM frameworks like Caliburn.Micro or Prism provide or you can have a look at Prism's CompositeCommand approach, which allows you to create a command that other commands can attach to. You could create composite commands for your menu and dynamically attach or detach the real commands from the selected view model.

Related

Binding a button nested in DataTemplate

I need to bind a button's command inside a datatemplate like below:
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Content="-" Cursor="Hand" Width="50"
Background="Red" x:Name="removeButton"
Command="{Binding Remove}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
Unfortunately it does not work. How can I bind a command in a button insade a datatemplate?
I found that thread in the forum:
Bindings in nested WPF DataTemplates
but the method given by person, who answered this question, does not work as well. I think, that something has changed in WPF since this time, I would you grateful for your help.
If Remove is defined in the view model of the parent ListView, you could bind to it using a RelativeSource:
Command="{Binding DataContext.Remove,
RelativeSource={RelativeSource AncestorType=ListView}}"
You could also set the AncestorType to Window or UserControl depending on where the command property is defined and where the DataTemplate is applied.

WPF Set Visibility on DataTemplate UI Element from MVVM View Model

I have a control that is set up as a DataTemplate:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<DataTemplate x:Key="KEYBOARD_EN">
<StackPanel>
<Button Visibility="{Binding Path=RegisterButtonVisible}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
</StackPanel>
</DataTemplate>
In this DataTemplate there is a control on which I wish to set the Visibility from various view models:
<Button Visibility="{Binding Path=RegisterButtonVisible}" Style="{StaticResource ...} > Register </Button>
I do routed events with my control, so I tried to set up something similar, but no matter what I try, the RegisterButtonVisible property does not get picked up:
public partial class MainKeyboard : UserControl
{
public static DependencyProperty RegisterButtonVisibleProperty;
public Visibility RegisterButtonVisible
{
get { return (Visibility)GetValue(RegisterButtonVisibleProperty); }
set { SetValue(RegisterButtonVisibleProperty, value); }
}
static MainKeyboard()
{
RegisterButtonVisibleProperty = DependencyProperty.Register("RegisterButtonVisible", typeof (Visibility),
typeof (MainKeyboard));
}
}
In my ViewModel I do this:
public Visibility RegisterButtonVisible // get, set, raisepropchange, etc
My DataTemplate with the button in it is wrapped in a userControl:
<UserControl x:Class="Bleh.Assets.MainKeyboard"
x:Name="TheControl"
Unloaded="UserControl_Unloaded">
<Viewbox>
<Grid>
<ContentControl Name="ctrlContent" Button.Click="Grid_Click" />
</Grid>
</Viewbox>
and is used in my views like this:
<assets:MainKeyboard
RegisterButtonVisible="Collapsed"
Loaded="MainKeyboard_Loaded">
<b:Interaction.Triggers>
<b:EventTrigger EventName="Register">
<b:InvokeCommandAction Command="{Binding ConfirmEmailAddressCommand}"/>
</b:EventTrigger>
<b:EventTrigger EventName="Enter">
<b:InvokeCommandAction Command="{Binding EnterKeyCommand}"/>
</b:EventTrigger>
</b:Interaction.Triggers>
</assets:MainKeyboard>
Please note this attribute:
RegisterButtonVisible="Collapsed"
This is my dependency property. It shows up in intelliesense, so the CLR has registered it correctly, but it does NOT pick up the property assignment (Collapsed is ignored).
This makes me feel like it is very close, but I do remember someone telling me I can not do this, thus the EventTriggers (this is a common issue with datatemplates and MVVM apparently).
So one option is to use something in the Interaction namespace, like I do my event triggers ( I just need to fire a "Visibility" trigger on this button somehow, at least I figure).
What is the right ANY way to do this in MVVM?
Fixing your code
In order to make your existing code work, you need to tell need to tell WPF what object RegisterButtonVisible should be read from. If it's a user control, give the UserControl a name and then reference that element via ElementName, like so:
<UserControl ... lots of stuff here
x:Name="TheControl"
>
In your button binding:
<Button Visibility="{Binding ElementName=TheControl, Path=RegisterButtonVisible}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
Of course, if you can't do that because the button and the usercontrol are in different files, you can still use an ancestor binding:
<Button Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type assets:MainKeyboard}},
Path=RegisterButtonVisible}"
Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
which, for each button, will walk up to find the closest instance of assets:MainKeyboard and then bind to the RegisterButtonVisible property.
Using MVVM
If you want to achieve the same using MVVM (instead of on a control), you need to use a converter to convert a boolean to a visibility property, like so:
<Button Visibility="{Binding IsRegistrationAllowed, Converter={StaticResource BoolToVis}}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
Of course, that assumes that your DataContext is set up correctly and pointing at your ViewModel.

ActivateItem not working in Caliburn.Micro

I am using Caliburn Micro for MVVM. In my MainView (shell), I have two controls. One hosts a RibbonView and another ContentControl which loads contents depending on the RibbonView menu selection. Here is the MainView (shell)
MainView (shell)
<Window x:Class="HotelReservation.Main.Views.MainView">
<DockPanel>
<ContentControl x:Name="RibbonView" DockPanel.Dock="Top"/>
<Grid DockPanel.Dock="Bottom" VerticalAlignment="Stretch" >
<ContentControl x:Name="ActiveItem"/>
</Grid>
</DockPanel>
</Window>
RibbonView
<Ribbon Margin="0,-20,0,0">
<RibbonTab Header="Room Band">
<RibbonGroup>
<RibbonButton Label="List" x:Name="RoomBandMain"
LargeImageSource="/HotelReservation.Global;component/Images/room-band-list-icon.png">
</RibbonButton>
</RibbonGroup>
</RibbonTab>
</Ribbon>
RibbonViewModel
public class RibbonViewModel : Conductor<object> {
public void RoomBandMain() { //Load in ActiveItem of MainView
ActivateItem(container.GetExportedValue<RoomBandMainViewModel>());
}
}
As can be seen, I am trying to load RoomBandMainViewModel in the <ContentControl x:Name="ActiveItem"/> The issue is that it is not loaded and I get a blank screen even though ActivateItem(container.GetExportedValue<RoomBandMainViewModel>()) code runs. I think that the <ContentControl x:Name="ActiveItem"/> exists not in RibbonView but its parent MainView, and hence the ActivateItem doesn't work.
How to resolve this issue.
Edit:
I had to set the DataContext of the <ContentControl x:Name="ActiveItem"/> to RibbonViewModel, so that ActiveItem is now property of RibbonViewModel and not MainViewModel. MainViewModel looks like below
So the MainView (shell) is now as follows
<Window x:Class="Conductor_Main.Views.MainView">
<DockPanel>
<ContentControl x:Name="RibbonView" DockPanel.Dock="Top"/>
<Grid DockPanel.Dock="Bottom" VerticalAlignment="Stretch" Background="Green"
DataContext="{Binding RibbonView}">
<ContentControl x:Name="ActiveItem" />
</Grid>
</DockPanel>
</Window>
Now the <ContentControl x:Name="ActiveItem" /> actually belongs to the RibbonViewModel.
What you have here is some kind of lifecycle of your windows. This has to be handled by the parent window of your ActiveItem.
The way to get this done the caliburn.micro way is to have a Conductor above the ActiveItem. In your case this is the MainWindow.
Your RibbonViewModel can be a Conductor, too. But only for it's own children. There can be more than one conductor.
From the caliburn documentation
Once you introduce the notion of a Screen Activation Lifecycle into
your application, you need some way to enforce it. This is the role of
the ScreenConductor. When you show a screen, the conductor makes sure
it is properly activated.
Which is quite a direct way of saying: If you have activation / life cycle, then use a conductor.

Button and ToggleButton in wpf Toolbar using MVVM

I have a wpf Toolbar in a ToolbarTray inside my application which must host Buttons and ToggleButtons.
Can someone suggest me how to implement this behavior in MVVM?
The code below is what I have right now:
<ToolBarTray Margin="5,30,5,30" MinWidth="35" HorizontalAlignment="Center" Orientation="Vertical" Background="Transparent">
<ToolBar x:Name="ToolBarControl" HorizontalAlignment="Stretch" ItemsSource="{Binding ToolBarItems}" >
<ToolBar.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" HorizontalAlignment="Stretch" Margin="0,0,0,15"
Template="{Binding ToolBarIcon}"
cal:Message.Attach="[Event Click] = [Action Select()]"
IsEnabled="True"
ToolTip="{Binding Text}"/>
</DataTemplate>
</ToolBar.ItemTemplate>
</ToolBar>
</ToolBarTray>
Where the Button could be a normal Button or a ToggleButton.
Thanks in Advance.
In MVVM pattern, your model class will contain the properties (all data objects) which you want to bind and display in view. So ToolBarItems collection will be part of model.
Your view basically will contain the above code you have written. And in code behind file, there will be an object of type model class as a property.
Your viewmodel can initialize the model and view objects and bind the model to view's datacontext.

How to change from binding ObervableCollection to a TabControl to a ContentControl

I'm very new to WPF and am writing an application using this example as a starting point
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090025
I will only have one workspace visible at any one time, so I want to get rid of the TabControl and use something simple instead - probably a ContentControl, I'm really not sure but all it needs to do is have content in and be closable. So I am trying to replace this:
<DataTemplate x:Key="WorkspacesTemplate"><TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
with:
<DataTemplate x:Key="WorkspacesTemplate">
<ContentControl Content="{Binding ??}" ContentTemplate="{StaticResource ClosableTabItemTemplate}"/>
</DataTemplate>
but I don't know what to bind to. The code in the example seems to use CollectionViewSource to set the active workspace - it's the active workspace that I am interested in but I don't understand what TabControl is doing except that it's something to do with IsSynchronizedWithCurrentItem="True"
The template is invoked from here (Workspaces is the ObservableCollection of ViewModels):
<HeaderedContentControl Content="{Binding Path=Workspaces}" ContentTemplate="{StaticResource WorkspacesTemplate}" Header="Workspaces" Style="{StaticResource MainHCCStyle}"/>
and here is the ClosableItem template:
<DataTemplate x:Key="ClosableTabItemTemplate">
<DockPanel Width="120">
<Button
Command="{Binding Path=CloseCommand}"
Content="X"
Cursor="Hand"
DockPanel.Dock="Right"
Focusable="False"
FontFamily="Courier"
FontSize="9"
FontWeight="Bold"
Margin="0,1,0,0"
Padding="0"
VerticalContentAlignment="Bottom"
Width="16" Height="16"
/>
<ContentPresenter
Content="{Binding Path=DisplayName}"
VerticalAlignment="Center"
/>
</DockPanel>
</DataTemplate>
Please can someone explain what I need to do? Thanks
The WorkspacesTemplate is telling WPF how to display the Workspaces property, which, as you say is an ObservableCollection of ViewModels.
So, the WorkspacesTemplate says, display all these ViewModels in a tab control, and for each ViewModel, use the ClosableTabItemTemplate to display the ViewModel in a tab.
Since you only want one workspace visible at a time, you don't need to expose a collection of workspaces from your ViewModel, and you don't need a tab control to display them. You simply expose the one current workspace from your ViewModel and provide some XAML to display it.
If you still want to use a template to wrap the ViewModel, then yes, you can just use a ContentControl to invoke the template:
<DataTemplate x:Key="MySingleWorkspaceTemplate">
<TextBlock Text={Binding Blah} />
<!-- etc -->
</DataTemplate>
and to invoke the template
<ContentControl Content="{Binding CurrentWorkspace}" ContentTemplate="{StaticResource MySingleWorkspaceTemplate}"/>
However, if this is the only place that the XAML is going to be used, you might as well forget the template and just declare the XAML directly. Eg, (instead of ContentControl)
<TextBlock Text={Binding CurrentWorkspace.Blah} />
<!-- etc -->
EDITED TO ADD:
I think you might be getting confused because currently the ViewModel has no concept of the "Selected Workspace", it just exposes a collection. For completeness (but don't worry about all this), the selection is introduced by the TabControl which indirectly uses the default CollectionView for the Workspaces collection, and CollectionView has the concept of a selected item. This is all in the view.
I wouldn't worry about any of this now, just expose the one workspace yourself from your ViewModel.
EDIT2:
Your close button is appearing because you are explicitly setting a ContentTemplate on your HeaderedContentControl. This template will appear regardless of Content.
To make a template only appear when there is data in Content, make the template implicit instead. If you add a DataType to your template definition (and remove the key), you tell WPF to always use this template to display an object of that data type.
<DataTemplate DataType="{x:Type vm:WorkspaceViewModel}">
<!-- Blah -->
</DataTemplate>
Then you can remove the explicit template from your HeaderedContentControl. Simply setting the Content will be enough to invoke the template, and if there is no Content, there is no template.
<HeaderedContentControl Content="{Binding Path=CurrentWorkspace}" />
(ps. If you're not using the header of HeaderedContentControl, you might as well just use a bog standard ContentControl)

Resources