I have following xaml:
<DataGridTemplateColumn Header="123">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Role}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Roles,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Path = DataContext.UpdateUserCommand}" /> // bind fails there
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
The problem is that my command is not running on change, but if move combobox definition out of datagrid, my command is firing successfully. Seems like my bindging wrong, but i can't figure out what's wrong.
Just bind the ComboBox's SelectedItem property to a SelectedRole property in your view model.
In order to run an async action when the view model property changes, just attach an async PropertyChanged event handler in the view model:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel()
{
PropertyChanged += async (s, e) =>
{
if (e.PropertyName == nameof(SelectedRole))
{
await SomeAsyncAction();
}
};
}
private Role selectedRole;
public Role SelectedRole
{
get { return selectedRole; }
set
{
selectedRole = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(SelectedRole)));
}
}
private async Task SomeAsyncAction()
{
...
}
}
You could try this (not 100% sure it'll work because cannot see all of your code)
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=DataContext.UpdateUserCommand}" />
Related
Hello I have looked everywhere and I can't understand what I am doing wrong.
My command is called and the onpropertychanged event is called but the ContentControl in my MainWindow will not show the next view.
I am new to WPF and Xaml and really trying to figure this out.
I have attached a link to the sample application on my Github.
The End game here is I want the Radio button to be checked when the view is active. I also want to be able to change from the home view to other views via a button on that view once I am past this first hurtle.
Github repository
public class ApplicationViewModel : ObservableObject
{
private ICommand _changeViewCommand;
private IViewModel _CurrentViewModel;
private List<IViewModel> _viewModels;
private bool _isActive;
public ApplicationViewModel()
{
ViewModels.Add(new HomeViewModel());
ViewModels.Add(new AccountViewModel());
CurrentViewModel = ViewModels.Find(r => r.Name == "Home");
CurrentViewModel.IsActive = true;
}
public List<IViewModel> ViewModels
{
get
{
if (_viewModels == null)
_viewModels = new List<IViewModel>();
return _viewModels;
}
}
public IViewModel CurrentViewModel
{
get
{
return _CurrentViewModel;
}
set
{
if (_CurrentViewModel != value)
{
_CurrentViewModel = value;
OnPropertyChanged();
}
}
}
public ICommand ChangePageCommand
{
get
{
if (_changeViewCommand == null)
{
_changeViewCommand = new RelayCommand(
p => ChangeViewModel((IViewModel)p),
p => p is IViewModel);
}
return _changeViewCommand;
}
}
private void ChangeViewModel(IViewModel viewModel)
{
CurrentViewModel = ViewModels.Find(r => r.Name == viewModel.Name);
}
public bool IsActive
{
get
{
return _isActive;
}
set
{
_isActive = value;
OnPropertyChanged("IsActive");
}
}
}
<UserControl.DataContext>
<viewModels:ApplicationViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<DataTemplate DataType="{x:Type viewModels:ApplicationViewModel}"/>
<DataTemplate DataType="{x:Type viewModels:HomeViewModel}">
<views:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:AccountViewModel}">
<views:AccountView/>
</DataTemplate>
</UserControl.Resources>
<StackPanel Orientation="Vertical">
<RadioButton Content="Home" IsChecked="{Binding IsActive, Mode=TwoWay}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding }">
<RadioButton.DataContext>
<viewModels:HomeViewModel/>
</RadioButton.DataContext>
</RadioButton>
<RadioButton Content="Account" IsChecked="{Binding IsActive, Mode=TwoWay}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding }">
<RadioButton.DataContext>
<viewModels:AccountViewModel/>
</RadioButton.DataContext>
</RadioButton>
</StackPanel>
you should connect RadioButtons to the ViewModels which you have in the list. don't create new view models in xaml.
and CurrentViewModel can be displayed via ContentControl with one of templates decalred in Resources
<UserControl.DataContext>
<viewModels:ApplicationViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<DataTemplate DataType="{x:Type viewModels:HomeViewModel}">
<views:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:AccountViewModel}">
<views:AccountView/>
</DataTemplate>
</UserControl.Resources>
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding ViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Name}"
IsChecked="{Binding IsActive}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding }">
</RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ContentControl Content="{Binding CurrentViewModel}"/>
</StackPanel>
after that you should probably change StackPanel to Grid to allow ContentControl to take all available screen space
Looks like if I assign the AccountViewModel to the button DataContext and set the RelativeSource AncestorType to x:Type Window the button on the HomeView will switch the view to the AccountView.
<Button Height="40" Content="Account" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding }">
<Button.DataContext>
<viewModels:AccountViewModel/>
</Button.DataContext>
</Button>
I have a combobox and I need a command in my view model to bind to its ContextMenuOpening event. I've tried referencing System.Windows.Interactivity and using InvokeCommandAction, but the command is not calling. Does anyone see where I'm going wrong?
<ComboBox x:Name="comboBoxAs" Grid.Column="0" VerticalAlignment="Top" Margin="928,62,0,0" Height="25"
ItemsSource="{Binding Source={StaticResource sas}}"
SelectedItem="{Binding Path=as, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource ComboBoxDefault}" HorizontalAlignment="Left" Width="212" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="ContextMenuOpening">
<i:InvokeCommandAction Command="{Binding ContextMenuOpeningCommand, Mode=OneWay}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
ViewModel:
public ICommand ContextMenuOpeningCommand
{
get
{
if (_contextMenuOpeningCommand == null)
{
_contextMenuOpeningCommand = new RelayCommand<object>(param => this.ContextMenuOpening(),
null);
}
return _contextMenuOpeningCommand;
}
}
public void ContextMenuOpening()
{
System.Windows.MessageBox.Show("test", "test");
}
private ICommand _contextMenuOpeningCommand;
Please try DropDownOpened to see whether the command gets hit. I tried it and it works here. Hope this helps :)
I have a Datagrid and don't like my workaround to fire a double click command on my viewmodel for the clicked (aka selected) row.
View:
<DataGrid EnableRowVirtualization="True"
ItemsSource="{Binding SearchItems}"
SelectedItem="{Binding SelectedItem}"
SelectionMode="Single"
SelectionUnit="FullRow">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<cmd:EventToCommand Command="{Binding MouseDoubleClickCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
...
</DataGrid>
ViewModel:
public ICommand MouseDoubleClickCommand
{
get
{
if (mouseDoubleClickCommand == null)
{
mouseDoubleClickCommand = new RelayCommand<MouseButtonEventArgs>(
args =>
{
var sender = args.OriginalSource as DependencyObject;
if (sender == null)
{
return;
}
var ancestor = VisualTreeHelpers.FindAncestor<DataGridRow>(sender);
if (ancestor != null)
{
MessengerInstance.Send(new FindDetailsMessage(this, SelectedItem.Name, false));
}
}
);
}
return mouseDoubleClickCommand;
}
}
I want to get rid of the view related code (the one with the dependency object and the visual tree helper) in my view model, as this breaks testability somehow. But on the other hand this way I avoid that something happens when the user doesn't click on a row but on the header for example.
PS: I tried having a look at attached behaviors, but I cannot download from Skydrive at work, so a 'built in' solution would be best.
Why don't you simply use the CommandParameter?
<DataGrid x:Name="myGrd"
ItemsSource="{Binding SearchItems}"
SelectedItem="{Binding SelectedItem}"
SelectionMode="Single"
SelectionUnit="FullRow">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<cmd:EventToCommand Command="{Binding MouseDoubleClickCommand}"
CommandParameter="{Binding ElementName=myGrd, Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
...
</DataGrid>
Your command is something like this:
public ICommand MouseDoubleClickCommand
{
get
{
if (mouseDoubleClickCommand == null)
{
mouseDoubleClickCommand = new RelayCommand<SearchItem>(
item =>
{
var selectedItem = item;
});
}
return mouseDoubleClickCommand;
}
}
EDIT: I now use this instead of Interaction.Triggers:
<DataGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
Command="{Binding Path=MouseDoubleClickCommand}"
CommandParameter="{Binding ElementName=myGrd, Path=SelectedItem}" />
</DataGrid.InputBindings>
Here is how you could implement it using an attached behaviour:
EDIT: Now registers behaviour on DataGridRow rather than DataGrid so that DataGridHeader clicks are ignored.
Behaviour:
public class Behaviours
{
public static DependencyProperty DoubleClickCommandProperty =
DependencyProperty.RegisterAttached("DoubleClickCommand", typeof(ICommand), typeof(Behaviours),
new PropertyMetadata(DoubleClick_PropertyChanged));
public static void SetDoubleClickCommand(UIElement element, ICommand value)
{
element.SetValue(DoubleClickCommandProperty, value);
}
public static ICommand GetDoubleClickCommand(UIElement element)
{
return (ICommand)element.GetValue(DoubleClickCommandProperty);
}
private static void DoubleClick_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var row = d as DataGridRow;
if (row == null) return;
if (e.NewValue != null)
{
row.AddHandler(DataGridRow.MouseDoubleClickEvent, new RoutedEventHandler(DataGrid_MouseDoubleClick));
}
else
{
row.RemoveHandler(DataGridRow.MouseDoubleClickEvent, new RoutedEventHandler(DataGrid_MouseDoubleClick));
}
}
private static void DataGrid_MouseDoubleClick(object sender, RoutedEventArgs e)
{
var row= sender as DataGridRow;
if (row!= null)
{
var cmd = GetDoubleClickCommand(row);
if (cmd.CanExecute(row.Item))
cmd.Execute(row.Item);
}
}
}
Xaml:
<DataGrid x:Name="grid" EnableRowVirtualization="True"
SelectedItem="{Binding SelectedItem}"
SelectionMode="Single"
SelectionUnit="FullRow" ItemsSource="{Binding SearchItems}">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Behaviours.DoubleClickCommand" Value="{Binding ElementName=grid, Path=DataContext.SortStateCommand}"/>
</Style>
</DataGrid.RowStyle>
You will then need to modify your MouseDoubleClickCommand to remove the MouseButtonEventArgs parameter and replace it with your SelectedItem type.
Way simpler than any of the proposed solutions here.
I'm using this one.
<!--
requires IsSynchronizedWithCurrentItem
for more info on virtualization/perf https://stackoverflow.com/questions/9949358/datagrid-row-virtualization-display-issue
-->
<DataGrid ItemsSource="{Binding SearchItems}"
IsSynchronizedWithCurrentItem="True"
AutoGenerateColumns="false" CanUserAddRows="False" CanUserDeleteRows="False" IsReadOnly="True" EnableRowVirtualization="True"
>
<!-- for details on ICollection view (the magic behind {Binding Accounts/} https://marlongrech.wordpress.com/2008/11/22/icollectionview-explained/ -->
<DataGrid.InputBindings>
<MouseBinding
MouseAction="LeftDoubleClick"
Command="{Binding MouseDoubleClickCommand}"
CommandParameter="{Binding SearchItems/}" />
</DataGrid.InputBindings>
</DataGrid>
from WPF DataGrid: CommandBinding to a double click instead of using Events
You may try this workaround:
<DataGrid EnableRowVirtualization="True"
ItemsSource="{Binding SearchItems}"
SelectedItem="{Binding SelectedItem}"
SelectionMode="Single"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTemplateColumn Header=".....">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock .....>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<cmd:EventToCommand Command="{Binding MouseDoubleClickCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</DataTemplate>
...................
In this case you have to specify DataTemplate for each column in the DataGrid
How can i do a leftclick on a datagrid only enable when a row is selected?
here are a example:
<DataGrid>
<DataGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding LeftClickCommand}"/>
</DataGrid.InputBindings>
</DataGrid>
Now when i click on no row in the datagrid the command is also executed, but i only wants to execute it when i click on a row.
you can test if the selected item is null
this is just a way that maybe help you
XAML:
<Custom1:DataGrid IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Persons}" SelectedItem="{Binding SelectedPerson}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<Custom:EventToCommand CommandParameter="{Binding SelectedItem, ElementName=dg}" Command="{Binding AfficheCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Code behind :
private PersonModel _selectedPersons = new PersonModel();
public PersonModel SelectedPerson
{
set
{
_selectedPersons = value;
}
}
public ICommand AfficheCommand
{
get
{
return new DelegateCommand(AfficheText);
}
}
private void AfficheText()
{
if(_selectedPersons != null)
MessageBox.Show(_selectedPersons.FirstName);
_selectedPersons = null;
}
I know that is not the best way but a way until you found a better idea ;)
I want to bind multiple buttons dynamically in MVVM.
1.I Dynamically created buttons using ItemControl
2. It did not Invoke Trigger Click Event.
Please help me on this.
<ItemsControl ItemsSource="{Binding ComponentList,Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Tag="{Binding WorkFlowCompId}">
<Button.Content>
<TextBlock Text="{Binding ComponentName,Mode=TwoWay}"/>
</Button.Content>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding ComponentSelected}"
CommandParameter="{Binding WorkFlowCompId,Mode=TwoWay}" >
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Your problem is that the command is getting the context from its template and there it cannot access the root of the ViewModel. Add this class to your solution:
public class DataContextProxy : FrameworkElement
{
public DataContextProxy()
{
this.Loaded += new RoutedEventHandler(DataContextProxyLoaded);
}
void DataContextProxyLoaded(object sender, RoutedEventArgs e)
{
Binding binding = new Binding();
if (!String.IsNullOrEmpty(BindingPropertyName))
{
binding.Path = new PropertyPath(BindingPropertyName);
}
binding.Source = this.DataContext;
binding.Mode = BindingMode;
this.SetBinding(DataContextProxy.DataSourceProperty, binding);
}
public Object DataSource
{
get { return (Object)GetValue(DataSourceProperty); }
set { SetValue(DataSourceProperty, value); }
}
public static readonly DependencyProperty DataSourceProperty =
DependencyProperty.Register("DataSource", typeof(Object), typeof(DataContextProxy), null);
public string BindingPropertyName { get; set; }
public BindingMode BindingMode { get; set; }
}
then use it in you XAML like so:
<UserControl.Resources>
<library:DataContextProxy x:Key="DataContextProxy"/>
</UserControl.Resources>
Then in your command binding:
<Button Tag="{Binding WorkFlowCompId}">
<Button.Content>
<TextBlock Text="{Binding ComponentName,Mode=TwoWay}"/>
</Button.Content>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding DataSource.ComponentSelected, Source={StaticResource DataContextProxy}"
CommandParameter="{Binding WorkFlowCompId,Mode=TwoWay}" >
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
A first pass at what you Xaml should look like:-
<ItemsControl ItemsSource="{Binding ComponentList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding SelectComponent}">
<TextBlock Text="{Binding ComponentName}"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I suspect as Derek alludes to in his comment you have a ComponentSelected command on the container. However you should move this command ot the view model for the component. Note I've also renamed it to SelectComponent so that is sounds like an action rather than a property.
TwoWay binding has been removed it wouldn't be doing anything in this case. Assigning a Tag value from a simple binding should be setting off alarm bells that the design is having some problems.
BTW, since you are doing a form of selection would not a ListBox be more appropriate in this case?