I am working on wizard kind of application, where I am maintaining the wizards in
a list named List<ViewmodelsForWizard>.
I have used same UserControl UCView to render two different pages by simply tweaking the ViewModel values stored in List<ViewmodelsForWizard>.
The problem is that SelectionChangedCommand for previous page gets fired on load of the next page. (Both pages uses same UserControl UCView and ViewModel)
MainWindow
<Grid DataContext="{Binding currentWizard}">
<Grid.Resources>
<DataTemplate DataType="{x:Type viewmodel}">
<local:UCView DataContext="{Binding}"/>
</DataTemplate>
</Grid.Resources>
<ContentControl Content="{Binding}" />
</Grid>
I have following ViewModel:
//all other properties and commands
private ICommand selectionChangedCommand;
public ICommand SelectionChangedCommand
{
get
{
if (selectionChangedCommand == null)
selectionChangedCommand = new DelegateCommand(OnSelectionChanged());
return selectionChangedCommand;
}
set
{
selectionChangedCommand = value;
}
}
//all other properties and commands
Selectionchanged in UCView
<ComboBox ItemsSource="{Binding items}"
DisplayMemberPath="data"
SelectedValue="{Binding selected}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectionChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
Any help would be great. Thanks.
Related
I have a simple TreeView that I'm trying to create a custom DataTemplate for. It displays as desired, but when I attempt to select the TreeViewItem, the highlighting does not work if I click the text in the TreeView. However, if I select just to the left of the text, it works:
The source is pretty straight forward, so I'm guessing I'm just missing a styling connection:
xaml
<TreeView x:Name="treeView"
ItemsSource="{Binding TreeViewItems}"
Grid.Row="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
<TreeViewItem Header="{Binding Header}">
<TreeViewItem.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{Binding Command}" />
</TreeViewItem.InputBindings>
</TreeViewItem>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
I've tried adding this to the xaml, but it didnt help:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}"
BasedOn="{StaticResource {x:Type TreeViewItem}}" />
</TreeView.ItemContainerStyle>
TreeViewModel
public class TreeViewModel : BaseNotifyModel, ITreeViewModel
{
public TreeViewModel(ITreeViewService menuService)
{
TreeViewItems =
new ReadOnlyObservableCollection<MenuItemViewModel>(menuService.TreeViewMenu);
}
public ReadOnlyObservableCollection<MenuItemViewModel> TreeViewItems
{
get
{
return Get<ReadOnlyObservableCollection<MenuItemViewModel>>();
}
private set
{
Set(value);
}
}
}
MenuItemViewModel
public class MenuItemViewModel : BaseNotifyModel
{
public MenuItemViewModel()
{
MenuItems =
new ObservableCollection<MenuItemViewModel>();
}
public String Header
{
get
{
return Get<String>();
}
set
{
Set(value);
}
}
public ICommand Command
{
get
{
return Get<ICommand>();
}
set
{
Set(value);
}
}
public ObservableCollection<MenuItemViewModel> MenuItems
{
get
{
return Get<ObservableCollection<MenuItemViewModel>>();
}
set
{
Set(value);
}
}
}
The TreeView is creating a TreeViewItem for each item in ItemsSource, so don't nest another TreeViewItem inside the one that the TreeView already created for you. That doesn't serve any purpose. Your template should just be providing a way for the existing TreeViewItem to display whatever's in its DataContext (your MenuItemViewModel, in this case).
You want to display the Header property in the tree view item; so just do that. Nothing fancy, just a Label or ContentControl, or even a TextBlock if it's String (though it's a lot of fun in WPF to have the flexibility of arbitrary content). When the user clicks on the content, your command executes. The only thing the user can see in the tree is the content. That's the only visible part of the TreeViewItem item, so that's what the user is going to click on.
Problem number two: Once the input binding is getting LeftClick, that breaks selection in the TreeView. It appears to me that you can't get there from here by that method.
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
<ContentControl
Content="{Binding Header}"
Background="Transparent"
>
<ContentControl.InputBindings>
<!-- This invokes the command, but breaks selection -->
<MouseBinding MouseAction="LeftClick"
Command="{Binding Command}" />
</ContentControl.InputBindings>
</ContentControl>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
Here's what you can do:
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
<ContentControl
Content="{Binding Header}"
Background="Transparent"
>
</ContentControl>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<EventSetter Event="Selected" Handler="MenuTreeViewItem_Click" />
</Style>
</TreeView.ItemContainerStyle>
Codebehind
private void MenuTreeViewItem_Click(object sender, RoutedEventArgs e)
{
((MenuItemViewModel)((FrameworkElement)sender).DataContext).Command.Execute(null);
}
There is a way to bind an event to a command in pure XAML, but it requires some C# code (LOL). But I mean, it's "pure XAML" in the sense that it's a nice generalized reusable attached behavior, not an unsightly event handler in your codebehind. Instead, it does exactly what I did above, but it does it in code that you can more easily avert your eyes from, and that you can reuse in pure XAML.
With help from #Ed Plunket and #Evk, I found a solution that will work. I switched to a ContentPresenter and used the Interaction.Triggers to call the command on the MouseLeftButtonUp action.
<TreeView ItemsSource="{Binding TreeViewItems}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
<ContentPresenter Content="{Binding Header}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding Path=DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ContentPresenter>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
In my xaml file with DataTemplates I want to add a DataContextChanged event to my ListBox that is in one of the templates, so i do this:
<DataTemplate x:Key="MyTemplate">
<ListBox Background="Transparent"
DataContext="{Binding Source={StaticResource Locator}}"
DataContextChanged="MyListBox_DataContextChanged"
SelectedItem="{Binding MyViewModel.SelSegment, Mode=TwoWay}"/>
</DataTemplate>
But in which file to implement "MyListBox_DataContextChanged"?
when working with Mvvm you don't handle events directly like you do when working with code behind, in your case the command that handles the DataContextChanged should be implemented in the corresponding ViewModel of the page where this DataTemplate is used,
and finally using a simple hack you can execute the associated Command when the DataContextChanged Event accured, your code should looks like so :
The Xaml :
<DataTemplate x:Key="MyTemplate">
<ListBox Background="Transparent"
DataContext="{Binding Source={StaticResource Locator}}"
SelectedItem="{Binding MyViewModel.SelSegment, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="DataContextChanged">
<command:EventToCommand Command="{Binding Mode=OneWay,Path=MyViewModel.MyListBox_DataContextChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</DataTemplate>
and Add the following command to your ViewModel :
private RelayCommand __myListBox_DataContextChangedCommand;
public RelayCommand MyListBox_DataContextChangedCommand
{
get
{
return __myListBox_DataContextChangedCommand
?? (__myListBox_DataContextChangedCommand = new RelayCommand(
() =>
{
//Your Event's Handler Goes Here
}));
}
}
Edit :
You could read more about EventToCommand at
Commands, RelayCommands and EventToCommand
I have a TreeView control in my WPF program,which gets data from MySQL server and show databases and tables:
Server
...Databases
...Tables
What I want is when I click an item in the Treeview,I can get the Server/Databases/Tables' name.However,after hours testing,I still could not get the name.
HierarchicalDataTemplate of server
<HierarchicalDataTemplate
DataType="{x:Type local:ServerViewModel}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="3,0" Source="Figures/server.png" />
<TextBlock Text="{Binding ServerName}" />
</StackPanel>
</HierarchicalDataTemplate>
The other two are similar, except that HierarchicalDataTemplate of table does not have ItemsSource.
As I'm using MvvmLight pattern,I pass the SelectedItem as CommandParameterto ViewModel:
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<cmd:EventToCommand
Command="{Binding tableSelected}"
CommandParameter="{Binding SelectedItem,ElementName=MyTreeview}"
PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In the ViewModel,I delared a command to handle SelectedItemChanged event.I implemented the Execute() method as below,yet it did not work.Whenever I click a TreeviewItem,an unhandled exception System.Reflection.TargetInvocationException would be thrown.
private void GetSelectedItem(object parameter)
{
var item = parameter as TreeViewItem;
StackPanel stackpanel = (StackPanel)VisualTreeHelper.GetChild(item, 0);
TextBlock textblock = (TextBlock)VisualTreeHelper.GetChild(stackpanel, 1);
MessageBox.Show(textblock.Text);
}
Update
Command
public ICommand tableSelected { get; private set; }//In the ViewModel
tableSelected = new RelayCommand<object>((obj) => GetSelectedItem(obj), (obj) => true);
//Implemented in the ViewModel's constructor
I've read several related posts on Stackoverflow,like How I can get content of TreeViewItem in WPF?,WPF: Getting TreeViewItem's constituent controls,yet I still can not get it right.Please help,thanks.
I realized that what I was doing previously is totally wrong.The SelectedItem is not a collections of UI control,but purely an instance the same type as that of DataType.
<HierarchicalDataTemplate
DataType="{x:Type local:DatabaseViewModel}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="3,0" Source="Figures/database.png" />
<TextBlock Text="{Binding DatabaseName}" />
</StackPanel>
</HierarchicalDataTemplate>
The code above is part of my treeview.If I click a treeviewitem which uses this template,what I get is a DatabaseViewModel instance,rather than a StackPanel or its children.This is really misleading for WPF beginners.
anybody an idea why CommandParameter is always null?
The class TransactionViewModel has the collection property of TransactionCommands to be displayed in the ItemsControl. The items are of type CommandViewModel.
TransactionBrowserViewModel has the command AddJobForSelectedTransactionCommand. The command to be passed as a parameter the CommandViewModel.
View Snipp:
<ItemsControl Grid.Row="4"
ItemsSource="{Binding TransactionCommands}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<telerik:RadButton Content="{Binding DisplayName}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}"
Command="{Binding ViewModel.AddJobForSelectedTransactionCommand, ElementName=userControl}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Codebehind of UserControl:
[Export]
public partial class TransactionBrowserView : UserControl, IView<TransactionBrowserViewModel>
{
[ImportingConstructor]
public TransactionBrowserView()
{
InitializeComponent();
}
[Import]
public TransactionBrowserViewModel ViewModel
{
get { return (TransactionBrowserViewModel)this.DataContext; }
set { this.DataContext = value; }
}
}
OK, sorry I have found the error.
It is located on the RadButton by Telerik. I have tested the scenario with a default button. Here it works without any problems.
You have set the ComandParameter to the path of the DataContext of the RadButton, but I don't see that you have set anything to that DataContext anywhere.
Look into the Output window for information regarding your Binding errors... it should say something like 'There is no DataContext property on object XXX'.
What are you trying to bind to the CommandParameter property?
Try this binding
<ItemsControl x:Name="transactionList" Grid.Row="4" ItemsSource="{Binding TransactionCommands}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<telerik:RadButton Content="{Binding DisplayName}"
CommandParameter="{Binding SelectedItem, ElementName=transactionList}"
Command="{Binding ViewModel.AddJobForSelectedTransactionCommand, ElementName=userControl}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Give your ItemsControl (like transactionList) and set the binding of the CommandParameter to the SelectedItem of your transactionList.
or does this not do what you want.
<telerik:RadButton Content="{Binding DisplayName}"
CommandParameter="{Binding}"
Command="{Binding ViewModel.AddJobForSelectedTransactionCommand, ElementName=userControl}"/>
I have a wpf, mvvm app, using the catel (http://catel.codeplex.com) framework\toolkit, C# .Net 4.0. The app has a ListBox with a TextBlock and ComboBox. The ListBox and ComboBox are populated from 2 different ObservableCollection from the ViewModel. I need to save (to a db), when the user clicks a button, each row in the ListBox where the user has selected an item from the ComboBox. The SelectionChanged event does not fire for any of the ComboBoxes in the ListBox. The idea being that I add to a list (ArrayList or IList?), in the ViewModel, each time the user selects an item in a ComboBox and for what row the item has been selected.
Or am I going about this the wrong way by trying to use the ComboBoxe SelectionChanged event? I also tried iterating thru the ListBox.Items but this seems like a hak and I want to avoid ui element logic in the ViewModel if possible.
The xaml:
<Grid>
<StackPanel Orientation="Horizontal">
<Label Width="180">Field1</Label>
<ListBox Height="200"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding List1, Mode=OneWay}"
Name="listBox1"
SelectionMode="Single"
Width="300">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Width="290">
<TextBlock Width="90" Text="{Binding}"></TextBlock>
<ComboBox Width="180" ItemsSource="{Binding DataContext.List2, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" DisplayMemberPath="Field1">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<catel:EventToCommand Command="{Binding SelectionChangedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}" DisableAssociatedObjectOnCannotExecute="False" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
ViewModel code:
//in the ViewModel constructor
SelectionChangedCommand = new Command<SelectionChangedEventArgs>(OnSelectionChangedCommandExecute, OnSelectionChangedCommandCanExecute);
public Command<SelectionChangedEventArgs> SelectionChangedCommand { get; private set; }
private bool OnSelectionChangedCommandCanExecute()
{
return true;
}
private void OnSelectionChangedCommandExecute(SelectionChangedEventArgs e)
{
// add or update list....
}
In Command binding you have used binding which has relative source binding...
consider making these changes in binding
1) using list box as Ancestortype
2) While binding use Path=DataContext.SelectionChangedCommand otherwise it will take list box as datacontext.
<catel:EventToCommand Command="{Binding Path=DataContext.SelectionChangedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}" DisableAssociatedObjectOnCannotExecute="False" PassEventArgsToCommand="True" />