How to bind a command to navigate between pages? - c#

I am learning navigation with MVVM as seen here:
Navigation with MVVM
In this tutorial, binding a command for button control is implemented by using ItemsControl like this:
<Window.Resources>
<DataTemplate DataType="{x:Type local:HomeViewModel}">
<local:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ProductsViewModel}">
<local:ProductsView />
</DataTemplate>
</Window.Resources>
<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>
If I understand it correctly, it iterates through collection of PageViewModels and binds a button command for every VIewModel. This example works fine and I am able to switch between pages with no problem.
However, I don't want to create buttons iteratively. I want to have two buttons inside a grid with the same command that switches views as in example above. Something like this:
<Window.Resources>
<DataTemplate DataType="{x:Type local:HomeViewModel}">
<local:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ProductsViewModel}">
<local:ProductsView />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Content="{Binding HomeView.Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding }"/>
<Button Grid.Column="1"
Content="{Binding ProductsView.Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding }"/>
</Grid>
But this doesn't work as I expected. I am able to bind View's names but not the Command. Clicking buttons is doing nothing. DataContext is set to an instance of my ApplicationViewModel. What could be a problem here?

The problem is in the CommandParameter="{Binding }", in the first example that binding is pointing to ItemsSource="{Binding PageViewModels}". The second example doesn't have that part, so that binding is failing.
EDIT>>>>
To clarify, you must bind the command parameter to an instanciated viewModel in your datacontext.
ViewModel:
public IPageViewModel homeViewModel{get; set;}
public IPageViewModel productsViewModel{get; set;}
...
IPageViewModel homeViewModel = new HomeViewModel();
IPageViewModel productsViewModel = new ProductsViewModel();
View:
CommandParameter="{Binding DataContext.homeViewModel, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
I dont have a code editor right now, but hope it helps.

Related

WPF - binding event to the ViewModel

I have a stack panel for which I need a mouse event to be handled in the ViewModel but the binding doesn't work - the command isn't found although the other elements are also binded to the ViewModel and they work. Here is the xaml:
<Window.DataContext>
<vm:Ticker/>
</Window.DataContext>
<ListView Grid.Row="3"
ItemsSource ="{Binding TickersCollectionView}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Name="STPListView">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction
Command="{Binding MouseLeftButtonDownCommand}"
CommandParameter="{Binding ElementName=STPListView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock
Width="130"
Text="{Binding Market}"
Foreground="WhiteSmoke"/>
<TextBlock
Width="110"
Text="{Binding Price}"
Foreground="{Binding LastPrice, Converter={StaticResource BoolToForeground}}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The command is a property of the ViewModel. How can I bind it?
If the MouseLeftButtonDownCommand property is defined in the same view model as the TickersCollectionView property, you should bind to it using a RelativeSource:
Command="{Binding DataContext.MouseLeftButtonDownCommand,
RelativeSource={RelativeSource AncestorType=ListView}}"

Bind to command from within a treeview

I'm building a treeview with HierarchicalDataTemplates and would like to bind the nodes to a command from my MainViewModel. I guess there is some conflict with the scopes, since the binding works if I e.g. use a button and define it outside of the treeview. If I define it inside, however, it does not work.
I've searched through Stackoverflow and found several solutions but none that worked for me. Jehof e.g. suggested here to use
<Button Command="{Binding DataContext.Command,
RelativeSource={RelativeSource AncestorLevel=2, AncestorType=TreeViewItem}}"
CommandParameter="{Binding}" />
but that did not work. Thank you for any suggestions!
<TreeView ItemsSource="{Binding _questions}" Grid.Row="0" Margin="10" BorderThickness="1">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Question}" ItemsSource="{Binding Converter={StaticResource QuestionConverter}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:MainOption}" ItemsSource="{Binding MainOptions}">
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Path=Name}" />
/////////////////////////////////////
<Button Command="{Binding ThisIsMyCommand}" Content="Click Me"/>
/////////////////////////////////////
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
Is your TreeView-Control inside a Window or UserControl?
If you are inside a Window:
<Button Command="{Binding DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}" CommandParameter="{Binding}" />
and for UserControl
<Button Command="{Binding DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" CommandParameter="{Binding}" />

How to bind mouse click to ContentPresenter within a HierarchicalDataTemplate to ViewModel?

I have the following DataTemplate used in a TreeView.
How do I have LeftMouseClick on the ContentPresenter to bind to a property of the DataType in MVVM fashion?
Thanks for any help (or better idea).
<DataTemplate x:Key="sharedTemplate">
<StackPanel Orientation="Horizontal">
<CheckBox
Focusable="False"
IsChecked="{Binding IsChecked}"
VerticalAlignment="Center"
/>
<ContentPresenter
Content="{Binding Name, Mode=OneTime}"
Margin="2,0"
/>
</StackPanel>
</DataTemplate>
<TreeView
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
ItemsSource="{Binding ReportTree}" >
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type r:ReportViewModel}"
ItemsSource="{Binding Children}"
>
<ContentControl Content="{Binding}"
ContentTemplate="{StaticResource sharedTemplate}" />
</HierarchicalDataTemplate>
................
Addendum: This works!
<DataTemplate DataType="{x:Type r:PrinterViewModel}">
<!--Bind the ContentControl to the DataType-->
<ContentControl Content="{Binding}"
ContentTemplate="{StaticResource sharedTemplate}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<h:EventToCommand
Command="{Binding DataContext.SelectItem, RelativeSource={RelativeSource FindAncestor, AncestorType=TreeView}}"
CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ContentControl>
</DataTemplate>
Addendum: As it turns out, the above Interaction.Trigger does work to correctly send the selected viewmodel back to the DataContext of the TreeView. Problem solved.

How to access dynamic control WPF in DataTemplate

I created a tabcontrol with TabItem dynamic, and each TabItem with a button to close it, but just want that button visible when the TabItem is selected.
But I can not access the control inside the DataTemplate
<TabControl Name="dynamicTab" ItemsSource="{Binding}" Margin="0,85,0,0">
<TabControl.Resources>
<DataTemplate x:Key="TabHeader" DataType="TabItem">
<DockPanel>
<Button
Focusable="False"
BorderThickness="0"
Background="Transparent"
BorderBrush="Transparent"
Padding="-4"
Height="10"
Width="10"
Name="btnDelete" Visibility="Hidden" DockPanel.Dock="Right" Margin="5,0,0,0" Click="btnDelete_Click"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Name}">
<Image Name="imgButtonClose" Source="/Recursos;component/Imagens/close16x16.png" Height="10" Width="10"/>
</Button>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}" />
</DockPanel>
</DataTemplate>
</TabControl.Resources>
</TabControl>
Just use the binding on the IsSelected property of ancestoral TabItem:
<BooleanToVisibilityConverter x:Key="boolToVisibilityConverter"/>
...
<Button ...
Name="btnDelete"
Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=IsSelected, Converter={StaticResource boolToVisibilityConverter}">
...
</Button>
If you have no problems with this binding:
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Name}"
then the proposed code should work.

WPF - Pass parameter to relaycommand within ItemsControl

I've got an ItemsControl for basically an array of comboboxes, textboxes and buttons:
The XAML for that bottom section is all within the ItemsControl: (Having a problem with the button, the last element)
<ItemsControl Grid.Row="2"
ItemsSource="{Binding CommandLinesOc}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Content="Send Command:"
HorizontalAlignment="Right"
Grid.Row="1" />
<ComboBox Grid.Column="1"
Grid.Row="1"
ItemsSource="{Binding ChromaCommandsCvs.View}"
SelectedItem="{Binding SelectedChromaCommand, UpdateSourceTrigger=PropertyChanged}" />
<Label Grid.Column="2"
Grid.Row="1"
Content="Parameter:"
HorizontalAlignment="Right" />
<TextBox Grid.Column="3"
Grid.Row="1"
Text="{Binding Parameter, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="4"
DataContext="ChromaGUI.MainWindow"
Grid.Row="1"
Content="Send"
HorizontalAlignment="Center"
FontSize="15"
Command="{Binding RelativeSource=
{RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}},
Path=DataContext.SendMessageCommand}"
CommandParameter="{Binding FullCommandString}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
As you can see, I've got the button element bound to a relay command. I had to use the main window relative source since the ItemsControl is setting the data context to an item in CommandLinesOc.
That all works fine - the problem is that CommandParameter seems to be inheriting the same relative source context, as I can't get FullCommandString (or any other property of one of the items in the observable collection, like Parameter or SelectedChromaCommand) to resolve to anything. So my question is, can I (and how do I) set the data context for the CommandParameter to be what the ItemsControl is giving me, while keeping the context for the Command the parent window?
Currently, you have the button DataContext set to a string "ChromaGUI.MainWindow" :
<Button DataContext="ChromaGUI.MainWindow"
.......
Command="{Binding RelativeSource=
{RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}},
Path=DataContext.SendMessageCommand}"
CommandParameter="{Binding FullCommandString}"/>
String doesn't have property FullCommandString, so your command parameter binding will fail. Simply remove DataContext setting in the button to make it use default DataContext which is corresponding item in the ItemsSource :
<Button
.......
Command="{Binding RelativeSource=
{RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}},
Path=DataContext.SendMessageCommand}"
CommandParameter="{Binding FullCommandString}"/>

Categories