How to make the binding happen from the ViewModel - silverlight

I'm trying to capture the Enter key being pressed on a text box, so that I can kick off an update to the server. It's not working, and so I've reduced the problem to it's simplist elemetns.
In this example, it seems that the binding is not happening per keystroke, but at sometime later. I need to the binding to be completed by the time the enter key is pressed. Consider the following XAML and function from the VM.
Here's the XAML of the textbox
<TextBox Text="{Binding TextValue, Mode=TwoWay}" Height="23" Width="300">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyDown">
<cmd:EventToCommand Command="{Binding KeyDownCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
The KeyDownCommand fire as expected, howerver the value is not yet in the TextValue property. If I hit enter a second time then the value is in the property? Here's the KeyDownCommand. The constructor of the ViewModel sets the keyDownCommand correctly.
public RelayCommand<RoutedEventArgs> KeyDownCommand { get; private set; }
private void KeyDownAction(RoutedEventArgs eventArg)
{
var source = eventArg.OriginalSource as FrameworkElement;
var e = eventArg as KeyEventArgs;
if (source != null && e != null && e.Key== Key.Enter)
{
e.Handled = true;
MessageBox.Show(TextValue);
}
}
It seems that what I need is a way to "post" the Text of the TextBox back to the TextValue property of the VM when the Enter key is pressed. Or is there something else I'm missing.

Try setting UpdateSourceTrigger to PropertyChanged on binding, like this:
<TextBox Text="{Binding TextValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="23" Width="300">
Now the view model property will be update every time the text is changed.
Update:
For Silverlight, as an alternative to UpdateSourceTrigger, you can use the following simple behavior that updates binding source whenever text changes:
public class TextChangedUpdateSourceBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.TextChanged += OnTextChanged;
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
var bindingExpression = AssociatedObject.GetBindingExpression(TextBox.TextProperty);
if (bindingExpression != null)
{
bindingExpression.UpdateSource();
}
}
}
Use it like this:
<TextBox Text="{Binding TextValue, Mode=TwoWay}" Height="23" Width="300">
<i:Interaction.Behaviors>
<b:TextChangedUpdateSourceBehavior />
</i:Interaction.Behaviors>
</TextBox>

No sooner did I post the question, than I hit upon the answer.
Here's the corrected KeyDownAction
private void KeyDownAction(RoutedEventArgs eventArg)
{
var source = eventArg.OriginalSource as FrameworkElement;
source.GetBindingExpression(TextBox.TextProperty).UpdateSource();
var e = eventArg as KeyEventArgs;
if (source != null && e != null && e.Key== Key.Enter)
{
e.Handled = true;
MessageBox.Show(TextValue);
}
}
Of now as I type this I realize that I'm "breaking" the pattern, in as much as now my ViewModel knows more about the View that it should.

Related

Why isn't my user control with a combobox binding correctly?

I've got a really simple UserControl I'm trying to create that contains a list of US states. I am trying to expose the selected state via a "SelectedState" property. However, I'm having trouble trying to get this binding to fire once it's hooked up in another UserControl / form.
The XAML for the user control looks like this:
<UserControl x:Class="Sample.Desktop.UserControls.StateDropdown"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Sample.Desktop.UserControls"
mc:Ignorable="d"
Width="170" Height="28"
d:DesignHeight="28" d:DesignWidth="170">
<ComboBox x:Name="cboState"
ItemsSource="{Binding StateList, RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedValue="{Binding SelectedState, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Abbreviation}"></Label>
<Label> - </Label>
<Label Content="{Binding Name}"></Label>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
In the code-behind, I have this code:
public static readonly DependencyProperty SelectedStateProperty = DependencyProperty.Register("SelectedState",
typeof(USState),
typeof(StateDropdown),
new UIPropertyMetadata(null,
new PropertyChangedCallback(OnSelectedStateChanged),
new CoerceValueCallback(OnCoerceSelectedState)));
private static object OnCoerceSelectedState(DependencyObject o, object value)
{
StateDropdown stateDropdown = o as StateDropdown;
if (stateDropdown != null)
return stateDropdown.OnCoerceSelectedState((USState)value);
else
return value;
}
private static void OnSelectedStateChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
StateDropdown stateDropdown = o as StateDropdown;
if (stateDropdown != null)
stateDropdown.OnSelectedStateChanged((USState)e.OldValue, (USState)e.NewValue);
}
protected virtual USState OnCoerceSelectedState(USState value)
{
// TODO: Keep the proposed value within the desired range.
return value;
}
protected virtual void OnSelectedStateChanged(USState oldValue, USState newValue)
{
// TODO: Add your property changed side-effects. Descendants can override as well.
}
public USState SelectedState
{
// IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
get
{
return (USState)GetValue(SelectedStateProperty);
}
set
{
SetValue(SelectedStateProperty, value);
}
}
I wasn't able to get the SelectedValue bound property of SelectedState to fire, so I ended up hooking up the SelectionChanged event.
private void cboState_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems?.Count > 0)
{
SelectedState = (USState)e.AddedItems[0];
}
}
In my other user control, I have this in the XAML:
<uc:StateDropdown Margin="10,0,0,0" SelectedState="{Binding SelectedState}" ></uc:StateDropdown>
And the ViewModel (I'm using Caliburn Micro), I have this property:
protected USState _selectedState;
public USState SelectedState
{
get { return _selectedState; }
set
{
_selectedState = value;
NotifyOfPropertyChange(() => SelectedState);
}
}
The combo is populated as expected. However, SelectedState is never fired/updated when I change the selection.
I had also previously tried using SelectedItem instead of SelectedValue, with the same results.
I'm sure I'm missing something obvious, but I'm having trouble seeing where I went wrong.
EDIT: Here's what fixed the binding.
I removed the SelectionChanged event. Then I modified my "hosting page" usercontrol to set TwoWay binding:
<uc:StateDropdown Margin="10,0,0,0" SelectedState="{Binding SelectedState, Mode=TwoWay}" ></uc:StateDropdown>
As soon as I added that, SelectedState started being updated when I changed the ComboBox value.
The only things I see, is this line :
SelectedValue="{Binding SelectedState, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
You don't need it, because of the SelectionChanged event. And it can cause the problem.
Also I would bind the SelectedState of the UserControl using a TwoWay binding.
Hope that will help you.

WPF - Dependency Property of Custom Control lost Binding at 2 way mode

I have this Custom Control
XAML:
<UserControl x:Class="WpfApplication1.UC"
...
x:Name="uc">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Horizontal">
<TextBox Text="{Binding Test, ElementName=uc}" Width="50" HorizontalAlignment="Left"/>
</StackPanel>
</UserControl>
C#
public partial class UC : UserControl
{
public static readonly DependencyProperty TestProperty;
public string Test
{
get
{
return (string)GetValue(TestProperty);
}
set
{
SetValue(TestProperty, value);
}
}
static UC()
{
TestProperty = DependencyProperty.Register("Test",typeof(string),
typeof(UC), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
public UC()
{
InitializeComponent();
}
}
And this is how i used that custom control:
<DockPanel>
<ItemsControl ItemsSource="{Binding Path=DataList}"
DockPanel.Dock="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" CommandParameter="{Binding}" Click="Button_Click"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<local:UC Test="{Binding SelectedString, Mode=OneWay}"/>
</DockPanel>
--
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
private ObservableCollection<string> _dataList;
public ObservableCollection<string> DataList
{
get { return _dataList; }
set
{
_dataList = value;
OnPropertyChanged("DataList");
}
}
private string _selectedString;
public string SelectedString
{
get { return _selectedString; }
set
{
_selectedString = value;
OnPropertyChanged("SelectedString");
}
}
public MainWindow()
{
InitializeComponent();
this.DataList = new ObservableCollection<string>();
this.DataList.Add("1111");
this.DataList.Add("2222");
this.DataList.Add("3333");
this.DataList.Add("4444");
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.SelectedString = (sender as Button).CommandParameter.ToString();
}
}
If I do not change text of UC, everything is ok. When I click each button in the left panel, button's content is displayed on UC.
But when I change text of UC (ex: to 9999), Test property lost binding. When I click each button in the left panel, text of UC is the same that was changed (9999). In debug I see that SelectedString is changed by each button click but UC's text is not.
I can 'fix' this problem by using this <TextBox Text="{Binding Test, ElementName=uc, Mode=OneWay}" Width="50" HorizontalAlignment="Left"/> in the UC.
But I just want to understand the problem, can someone help me to explain it please.
Setting the value of the target of a OneWay binding clears the binding. The binding <TextBox Text="{Binding Test, ElementName=uc}" is two way, and when the text changes it updates the Test property as well. But the Test property is the Target of a OneWay binding, and that binding is cleared.
Your 'fix' works because as a OneWay binding, it never updates Test and the binding is never cleared. Depending on what you want, you could also change the UC binding to <local:UC Test="{Binding SelectedString, Mode=TwoWay}"/> Two Way bindings are not cleared when the source or target is updated through another method.
The issue is with below line
<local:UC Test="{Binding SelectedString, Mode=OneWay}"/>
The mode is set as oneway for SelectString binding so text will be updated when the value from code base changes. To change either the source property or the target property to automatically update the binding source as TwoWay.
<local:UC Test="{Binding SelectedString, Mode=TwoWay}"/>

Binding PropertyChanged Dilemma

Oh lord is monday again and I feel like all my knowledge about wpf has been deleted just like that.
I thought when Binding in Mode PropertyChanged that the Source will be only updated when the Target property was changed and not all the time.
Here is an example where Binding keeps updating the Source even though the Target property hasn't been changed. Why?
Btw, I am in .NET 4.0
<StackPanel>
<TextBox x:Name="tbx1" Text="{Binding Txt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="Change Text" Click="OnClick" />
</StackPanel>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
private void OnClick(object sender, RoutedEventArgs e)
{
tbx1.Text = "hello";
}
}
public class ViewModel
{
private string txt;
public string Txt
{
get { return txt; }
set { txt = value; Console.WriteLine("Txt Setter Called!");}
}
}
Everytime I click on Button the setter of Txt is being called. Why? The value was not changed.
GetHashCode() method returns same results.
What am I missing??? :-)
Well, that's the way it's supposed to work, but the name might be a bit misleading.
The source value is being updated when the target property has been set, not necessarily when the value has changed, as the name suggests.
You can observe the same behavior by using fx. a CheckBox. Setting the IsChecked property to true over and over will also trigger a source update even though the target value does not change.
So the binding system does not compare the actual values before triggering an update, it just cares about whether the target property was set or not.
Your example extended with a CheckBox:
XAML:
<StackPanel>
<TextBox x:Name="tbx1" Text="{Binding Txt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<CheckBox x:Name="chk1" IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Change Text" Click="OnClick" />
</StackPanel>
Code-behind:
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
private void OnClick(object sender, RoutedEventArgs e)
{
tbx1.Text = "hello";
chk1.IsChecked = true;
}
}
public class ViewModel
{
private string txt;
public string Txt
{
get { return txt; }
set { txt = value; Console.WriteLine("Txt Setter Called!"); }
}
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set { isChecked = value; Console.WriteLine("IsChecked Setter Called!"); }
}
}
As you expect, there is no actual "property change" happening, which can be confirmed with:
using System.ComponentModel;
var descriptor = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox));
descriptor.AddValueChanged(tbx1, (s, e) => Console.WriteLine("tbx1 changed"));
"tbx1 changed" will only appear once.
If the source is a dependency property, it doesn't change either. Try adding another textbox and using it as the source instead of the viewmodel:
<TextBox Name="tbx2" />
<TextBox Name="tbx1" Text="{Binding ElementName=tbx2, Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Handler:
descriptor.AddValueChanged(tbx2, (s, e) => Console.WriteLine("tbx2 changed"));
Again, only one change.
So yes, the trigger is not accurately named. A reason for this behavior may be to ensure that a property set always triggers a binding converter, because a ConvertBack could return a different value even with the same input.
In any event, both target and source need to take some responsibility in determining what a "change" is. After all, if it's a true two-way binding, then we should be allowed to implement OnClick this way with exactly the same effect:
tbx1.DataContext.Txt = "hello";
So just make sure your property setters always check for an actual change before proceeding (as dependency properties do).
Change your BindingMode to OneWay or OneWayToSource.. your problem will get solve...
<TextBox x:Name="tbx1" Text="{Binding Txt, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"/>
You could change you setter to check if the new value is different from the old.
public string Txt
{
get { return txt; }
set {
if (txt == value) return;
txt = value;
Console.WriteLine("Txt Setter Called!");
}
}

How do I prevent keyboard navigation in an AutoCompleteBox from firing the SelectionChanged event?

I'm trying to build a search field using the AutoCompleteBox from the WPF Toolkit. The AutoCompleteBox's Text property is bound to a property in a ViewModel that implements INotifyPropertyChanged. When the property is changed, it fetches new suggestions to show to the user.
This gets mucked up if the user uses arrow keys to scan through the list of autocomplete suggestions before choosing one - the moment the cursor move into the popup, SelectionChanged is fired, the text field gets a new value, and the autocomplete suggestions are re-collected. This also interferes with my desire to use the SelectionChanged event to kick off a search.
Is there any way to prevent the SelectionChanged event from firing on keyboard navigation?
Here's how I have things set up. Note sc:SearchField is a subclass of AutoCompleteBox that only provides a way to access the TextBox property on the AutoCompleteBox so I can call functions like SelectAll()
XAML:
<sc:SearchField x:Name="SearchField" DataContext="{Binding SearchBoxVm}" Text="{Binding Query, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding QuerySuggestions, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" IsTextCompletionEnabled="False" Margin="54,10,117,67" Grid.RowSpan="2" BorderThickness="0" FontSize="14" PreviewKeyUp="searchField_OnKeyup" Foreground="{Binding Foreground, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontStyle="{Binding QueryFont, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
</sc:SearchField>
ViewModel:
void GetQuerySuggestions()
{
if (!string.IsNullOrEmpty(Query) && !Query.Equals(DEFAULT_TEXT))
{
QueryFont = FontStyles.Normal;
Foreground = Brushes.Black;
QuerySuggestions = SearchAssistant.GetQueryRecommendations(_query);
}
}
public string _query = DEFAULT_TEXT;
public string Query
{
get
{
return _query;
}
set
{
_query = value;
GetQuerySuggestions();
NotifyPropertyChanged("Query");
}
}
List<string> querySuggestions = new List<string>();
public List<string> QuerySuggestions
{
get { return querySuggestions; }
set
{
querySuggestions = value;
NotifyPropertyChanged("QuerySuggestions");
}
}
SearchField subclass:
public class SearchField : AutoCompleteBox
{
public TextBox TextBox
{
get
{
return (this.GetTemplateChild("Text") as TextBox);
}
}
}
Not sure if this is what you are wanting to do but I have the following code which only changes the selection when the 'Enter' key is pressed or the mouse is used to select an item from the list (left mouse button clicked). I can arrow up and down the list without issue and only fire the selection changed event when the user presses enter or clicks on the desired entry.
Note that I am using the AutoCompleteBox and not the SearchField as you are using.
In XAML:
<toolkit:AutoCompleteBox Name="OmniSearchTextBox"
ItemsSource="{Binding CompanyList}"
SelectedItem="{Binding SelectedObject, Mode=TwoWay}"
IsTextCompletionEnabled="False"
FilterMode="Contains"
KeyUp="OmniSearch_KeyUp"
MouseLeftButtonUp="OmniSearch_MouseLeftButtonUp"
Margin="10,94,10,0"
RenderTransformOrigin="0.518,1.92" Height="35"
VerticalAlignment="Top" />
In code behind:
private void OmniSearch_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
BindingExpression exp = this.OmniSearchTextBox.GetBindingExpression(AutoCompleteBox.SelectedItemProperty);
exp.UpdateSource();
}
}
private void OmniSearch_MouseLeftButtonUp(object sender, MouseEventArgs e)
{
BindingExpression exp = this.OmniSearchTextBox.GetBindingExpression(AutoCompleteBox.SelectedItemProperty);
exp.UpdateSource();
}
In ViewModel:
private const string CompanyListPropertyName = "CompanyList";
private ObservableCollection<Company> _companyList;
public ObservableCollection<Company> CompanyList
{
get
{
return _companyList;
}
set
{
if (_companyList == value)
{
return;
}
_companyList = value;
RaisePropertyChanged(CompanyListPropertyName);
}
}
private Company _selectedObject;
public Company SelectedObject
{
get
{
return _selectedObject;
}
set
{
if (_selectedObject != value)
{
_selectedObject = value;
}
}
}

MVVM - WPF DataGrid - AutoGeneratingColumn Event

I'm currently taking a good look at the excellent toolkit from Laurent and I have the following question.
From Blend 4, I have added an EventTrigger for the Loaded event, in my ViewModel I have the following:
public RelayCommand rcAutoGeneratingColumn { get; private set; }
In the constructor I have:
rcAutoGeneratingColumn =
new RelayCommand(o => DataGridAutoGeneratingColumn(o));
Also in the ViewModel, I have the method which I wish to be invoked by the RelayCommand:
private void DataGridAutoGeneratingColumn(Object o)
{
DataGrid grid = (DataGrid)o;
foreach (DataGridTextColumn col in grid.Columns)
{
if (col.Header.ToString().ToLower() == "id")
{
col.Visibility = System.Windows.Visibility.Hidden;
}
}
}
My XAML contains the following (for the DataGrid):
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<GalaSoft_MvvmLight_Command:EventToCommand
Command="{Binding rcAutoGeneratingColumn, Mode=OneWay}"
CommandParameter="{Binding ElementName=dataGrid1, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
There is NO PROBLEM here the code works just fine, but obviously the event used to hide certain columns should be the AutoGeneratingColumn event and not Loaded.
I have used to Loaded event as a getaround.
I was hoping that I could relay any event offered by the control so that, in this case, the following would work instead:
<i:Interaction.Triggers>
<i:EventTrigger EventName="AutoGeneratingColumn">
<GalaSoft_MvvmLight_Command:EventToCommand
Command="{Binding rcAutoGeneratingColumn, Mode=OneWay}"
CommandParameter="{Binding ElementName=dataGrid1, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
I am unable to get the AutoGeneratingColumn event to trigger, and I'm hoping that I've overlooked something and appreciate any advice given!
This behaviour is the same with the GridControl from DevExpress, in that the Loaded event is triggered whereas the ColumnsPopulated event (this being the equivalent of the AutoGeneratingColumn event) is not.
DevExpress offered the following information with regard to my question:
"We have reviewed this question, and come to an interesting conclusion. It looks like the visual tree is not being built at the moment when the Interaction.Triggers are being processed"
If this is true, and there is no other way in which to invoke the events within the ViewModel, then one would have to go ahead and - by using trial and error - note which of the DataGrid events (of which there are over 100) can be invoked in this way and which cannot!
One would like to think that every event which is available in the code-behind, can also be reached when applying the MVVM pattern.
I have searched for an answer but I cannot rule out that I have overlooked something, so if this is to be the case, then please accept my apologies!
You don't have to use evil code behind ;-) You can do this using an attached behaviour...
public class AutoGeneratingColumnEventToCommandBehaviour
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(AutoGeneratingColumnEventToCommandBehaviour),
new PropertyMetadata(
null,
CommandPropertyChanged));
public static void SetCommand(DependencyObject o, ICommand value)
{
o.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject o)
{
return o.GetValue(CommandProperty) as ICommand;
}
private static void CommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var dataGrid = d as DataGrid;
if (dataGrid != null)
{
if (e.OldValue != null)
{
dataGrid.AutoGeneratingColumn -= OnAutoGeneratingColumn;
}
if (e.NewValue != null)
{
dataGrid.AutoGeneratingColumn += OnAutoGeneratingColumn;
}
}
}
private static void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var dependencyObject = sender as DependencyObject;
if (dependencyObject != null)
{
var command = dependencyObject.GetValue(CommandProperty) as ICommand;
if (command != null && command.CanExecute(e))
{
command.Execute(e);
}
}
}
}
Then use it in XAML like this...
<DataGrid ItemsSource="{Binding MyGridSource}"
AttachedCommand:AutoGeneratingColumnEventToCommandBehaviour.Command="{Binding CreateColumnsCommand}">
</DataGrid>
Just set EventTrigger.SourceObject property.
<DataGrid
x:Name="DataGrid"
AutoGenerateColumns="True"
IsReadOnly="True"
ItemsSource="{Binding Data}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="AutoGeneratingColumn" SourceObject="{Binding ElementName=DataGrid}">
<local:EventToCommand
Command="{Binding ColumnGeneratingCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
As MVVMLight from Galasoft is deprecated now, we can use CommunityToolkit.Mvvm package and use it like this:
<DataGrid AutoGenerateColumns="True"
Name="DataGrid"
ItemsSource="{Binding Items}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="AutoGeneratingColumn" SourceObject="{Binding ElementName=DataGrid}">
<i:InvokeCommandAction Command="{Binding AutoGeneratingColumnCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
Note that Items property is a simple List, It could be an ObservableCollection or whatever.
The trick to get the fired event is to load your data after the window is loaded, or raise OnpropertyChanged on Items property after loaded.
<Window ...>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Window>
In your View Model:
private RelayCommand<DataGridAutoGeneratingColumnEventArgs> myAutoGeneratingColumnCommand;
public RelayCommand<DataGridAutoGeneratingColumnEventArgs> AutoGeneratingColumnCommand
{
get
{
if (myAutoGeneratingColumnCommand == null)
myAutoGeneratingColumnCommand = new RelayCommand<DataGridAutoGeneratingColumnEventArgs>(AutoGeneratingColumnCommandAction);
return myAutoGeneratingColumnCommand;
}
}
private void AutoGeneratingColumnCommandAction(DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyName == "Id")
{
e.Column.Width = 60;
}
else if (e.PropertyName == "Name")
{
e.Column.Header = "myName";
e.Column.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
}
else
e.Cancel = true; // ignore all other properties and remove their column
}
RelayCommand myLoadedCommand;
public RelayCommand LoadedCommand
{
get
{
if (myLoadedCommand == null)
myLoadedCommand = new RelayCommand(LoadedCommandAction);
return myLoadedCommand;
}
}
private void LoadedCommandAction()
{
Load(); // Populate the Items List
}
During the course of developing a project with MVVM you're going to have circumstances where you must handle events in your view's code-behind and EventToCommand just plain doesn't work. You especially find this with Silverlight, but I assume from your question that you're using WPF. It's okay to do some event handling in your view's code-behind, just don't put any business logic there. You can even leave the command in your view model, just call it directly from your event handler.
((YourViewModel)this.DataContext).rcAutoGeneratingColumn.Execute(sender);

Resources