DataGrid CellStyle Setters with Freezable StaticRecource - c#

I wanted to set the command of a button in a WPF datagrid with a setter. But it seems that the DP property CommandProperty gets ovewritten with its default value null after I returned a copy in
CreateInstanceCore(), so the original command gets lost.
If I bind the StaticResource directly it works without problems.
Is there a way to stop that behavior or another solution?
public class ResourceCommand : Freezable, ICommand {
public ICommand Command {
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
// Using a DependencyProperty as the backing store for Command. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(ResourceCommand), new UIPropertyMetadata(null, CommandPropertyChangedCallback));
static void CommandPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) {
ResourceCommand resourceCommand = (ResourceCommand)d;
int h = resourceCommand.GetHashCode();
if (e.OldValue != null)
((ICommand)e.OldValue).CanExecuteChanged -= resourceCommand.OnCanExecuteChanged;
if (e.NewValue != null)
((ICommand)e.NewValue).CanExecuteChanged += resourceCommand.OnCanExecuteChanged;
}
#region ICommand Member
public bool CanExecute(object parameter) {
if (Command == null)
return false;
return Command.CanExecute(parameter);
}
public event EventHandler CanExecuteChanged;
void OnCanExecuteChanged(object sender, EventArgs e) {
if (CanExecuteChanged != null)
CanExecuteChanged(sender, e);
}
public void Execute(object parameter) {
Command.Execute(parameter);
}
#endregion
protected override Freezable CreateInstanceCore() {
ResourceCommand ResourceCommand = new ResourceCommand();
ResourceCommand.Command = Command;
return ResourceCommand;
}
}
xaml:
<Window.Resources>
<local:ResourceCommand x:Key="FirstCommand" Command="{Binding FirstCommand}" />
<local:ResourceCommand x:Key="SecondCommand" Command="{Binding SecondCommand}" />
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding Collection}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Click me">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Command" Value="{StaticResource FirstCommand}" />
</Style>
</Button.Style></Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>

It works if you define your resource commands like this:
<local:ResourceCommand x:Key="FirstCommand" Command="{Binding FirstCommand}" x:Shared="False"/>
Using this technique you can even throw not-implemented in CreateInstanceCore and so you'll just be using Freezable to enable data binding.

Related

WPF Binding to Property of Element of ObservableCollection

I'm working with a DataGrid in WPF and I'm trying to perform some data binding that it a little more complex than I'm used to. I have an ObservableCollection of a class that also implements an ObservableCollection of a subclass. I'm trying to bind the IsChecked property of a CheckBox to a value on that subclass and no matter when I try I can't seem to get it to work. Hopefully I'm just missing something simple.
In my main program I have the following, and it works fine for detecting changes to "SomeProperty" on the "MyDevice" class:
ObservableCollection<MyDevice> MyDevices = new ObservableCollection<MyDevice>();
DevicesGrid.ItemSource = MyDevices;
My class definition is below:
public class MyDevice : INotifyPropertyChanged
{
public class Input : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged([CallerMemberName] string PropertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
private bool _SyncDetected;
public bool SyncDetected
{
get { return _SyncDetected; }
set { _SyncDetected = value; RaisePropertyChanged(); }
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged([CallerMemberName] string PropertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
private bool _SomeProperty;
public bool SomeProperty
{
get { return _SomeProperty; }
set { _SomeProperty = value; RaisePropertyChanged(); }
}
public ObservableCollection<Input> MyInputs = new ObservableCollection<Input>() { new Input(), new Input() };
}
And this is my XAML:
<DataGrid x:Name="DevicesGrid" Margin="10,80,10,10" AutoGenerateColumns="False">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource {x:Type DataGridRow}}">
<Setter Property="ContextMenu" Value="{StaticResource DeviceRowContextMenu}"/>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Sync/Hotplug" IsReadOnly="True" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="2,2,2,2" VerticalAlignment="Center" HorizontalAlignment="Center">
<CheckBox Margin="2,2,2,2" IsHitTestVisible="False" IsChecked="{Binding MyInputs[0].SyncDetected}" Content="In1"/>
<CheckBox Margin="2,2,2,2" IsHitTestVisible="False" IsChecked="{Binding MyInputs[1].SyncDetected}" Content="In2"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I'm really new at working with WPF so any help is appreciated. Thanks.
Here is something wrong:
public ObservableCollection<Input> MyInputs = new ObservableCollection<Input>() { new Input(), new Input() };
MyDevice.MyInputs is a field, not a property, so the binding system cannot find it through reflection.

Get Treeviewitem from Menu Context. WPF XAML MVVM

Good day.
I have created a TreeViewHelper Class in order to trigger events and pass through datacontext to functions. However. Id like to select the treeviewitem when right clicking.
My solution follows the MVVM pattern as close as possible. So if possible i'd like to avoid the Trigger Event feature that is provided.
Id love for some advice on how to tackle this issue.
here is the treeviewhelper.cs class
public static class TreeViewHelper
{
#region SelectedItem
public static object GetSelectedItem(DependencyObject obj)
{
return (object)obj.GetValue(SelectedItemProperty);
}
public static void SetSelectedItem(DependencyObject obj, object value)
{
obj.SetValue(SelectedItemProperty, value);
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewHelper), new UIPropertyMetadata(null, SelectedItemChanged));
private static void SelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs dpea)
{
if (!(obj is TreeView) || dpea.NewValue == null)
return;
var view = obj as TreeView;
view.SelectedItemChanged += (sender, e) => SetSelectedItem(view, e.NewValue);
var command = (ICommand)(view as DependencyObject).GetValue(SelectedItemChangedProperty);
if (command != null)
{
if (command.CanExecute(null))
command.Execute(new DependencyPropertyEventArgs(dpea));
}
}
#endregion
#region Selected Item Changed
public static ICommand GetSelectedItemChanged(DependencyObject obj)
{
return (ICommand)obj.GetValue(SelectedItemProperty);
}
public static void SetSelectedItemChanged(DependencyObject obj, ICommand value)
{
obj.SetValue(SelectedItemProperty, value);
}
public static readonly DependencyProperty SelectedItemChangedProperty =
DependencyProperty.RegisterAttached("SelectedItemChanged", typeof(ICommand), typeof(TreeViewHelper));
#endregion
#region Event Args
public class DependencyPropertyEventArgs : EventArgs
{
public DependencyPropertyChangedEventArgs DependencyPropertyChangedEventArgs { get; private set; }
public DependencyPropertyEventArgs(DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
this.DependencyPropertyChangedEventArgs = dependencyPropertyChangedEventArgs;
}
}
#endregion
}
Here is the XAML Code (shortened)
<TreeView x:Name="TestPlanTreeView"
ItemsSource="{Binding Data.TreeViewCollection}"
utils:TreeViewHelper.SelectedItem="{Binding CurrSelItem}"
utils:TreeViewHelper.SelectedItemChanged="{Binding SelItemChgCmd}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu ItemsSource="{Binding MenuCollection, RelativeSource={RelativeSource Self}}">
<ContextMenu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</ContextMenu.Resources>
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}"/>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<TextBlock Text="{Binding Header}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
This is how i trigger a function when selecting a new item.
SelItemChgCmd = new RelayCommand<DependencyPropertyEventArgs>(Data.TreeViewItemSelected);
CurrSelItem = new object();
public void TreeViewItemSelected(DependencyPropertyEventArgs dpe)
{
TreeViewItem selectedTreeViewItem = dpe.DependencyPropertyChangedEventArgs.NewValue as TreeViewItem;
ListViewCollection = BuildListViewCollection(selectedTreeViewItem);
}
What I would love to do, is to be able to select the treeviewitem before right clicking on the object. However, I am not sure how to do that with my given code without hacking it.
My original thoughts were to create a property in the treeviewhelper for PreviewRightMouseButtonUp and have that trigger the SelectedItem event, but i then run into issues with data.
Since the PreviewRightMouseButtonUp does not hold the same method types as the selectedItem Changed event, then i run into these data issues.
Id really appreciate some thoughts on this matter. Its been blocking me for a while now.

WPF DataGrid: Set ColumnWidth in a Trigger

I'm having problems to set the ColumnWidth of a DataGrid inside a trigger in his style.
I have this:
<DataGrid ItemsSource="{Binding Data}">
<DataGrid.Style>
<Style TargetType="DataGrid">
<Style.Triggers>
<DataTrigger Binding="{Binding Data.Count}" Value="2">
<Setter Property="Background" Value="LightGreen" />
<Setter Property="ColumnWidth" Value="400" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
</DataGrid>
In case of 2 rows I want to fill the background with green and to make wider columns, but I can't only achieve the green background.. Why the ColumnWidth setting is not working?
[![enter image description here][1]][1]
It works if I put the ColumnWidth setting outside the Trigger.. but I don't want this..
<DataGrid ItemsSource="{Binding Data}">
<DataGrid.Style>
<Style TargetType="DataGrid">
<Setter Property="ColumnWidth" Value="400" />
<Style.Triggers>
<DataTrigger Binding="{Binding Data.Count}" Value="2">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
</DataGrid>
Thanks!
Solved:
Finally I have established ColumnWidt with a Binding to my data and a converter:
<DataGrid ColumnWidth="{Binding Data, Converter={StaticResource DataToColumnWidthConverter}}" ItemsSource="{Binding Data}" IsReadOnly="True" MaxHeight="300" >
Converter:
[ValueConversion(typeof(DataTable), typeof(DataGridLength))]
public class DataToColumnWidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
DataTable dt = value as DataTable;
if (dt != null && dt.Rows.Count == 2)
{
return new DataGridLength(400);
}
return new DataGridLength();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
This is OK for me because my data won't change number of rows in execution time, so ColumnWidth only needs to be calculated one time at beginning.
Thanks all
Remove ItemsSource from your DataGrid, and move it into DataTrigger.
To set ColumnWidth after initial binding, re-binding is needed. You cannot set ColumnWidth from code, it won't have any effect. For ColumnWidth to have any effect, you need to first remove DataGrid's DataContext/ItemsSource (setting to null) and then re-assign it.
So, if you change your collection somewhere, you have to first set the DataContext of DataGrid to null, and then re-assign it. See what I have done in Button click below.
Code below is self explanatory. I have written a converter and MarkupExtension for cases when Data.Count will not be 4.
<DataGrid x:Name="Dgrid" Margin="0,58,0,0">
<DataGrid.Style>
<Style TargetType="DataGrid">
<Style.Triggers>
<DataTrigger Binding="{Binding Data.Count}" Value="4">
<Setter Property="ColumnWidth" Value="200" />
<Setter Property="ItemsSource" Value="{Binding Data}" />
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
<DataTrigger Binding="{Binding Data.Count, Converter={local:CountToBool}}" Value="true">
<Setter Property="ColumnWidth" Value="100" />
<Setter Property="ItemsSource" Value="{Binding Data}" />
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
</DataGrid>
Converter :
public class CountToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((int)value != 4)
return true;
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class CountToBoolExtension : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new CountToBoolConverter();
}
}
CodeBehind :
ViewModel vm = new ViewModel();
// removing 2 items and reassigning DataContext to viewmodel.
private void Button_Click(object sender, RoutedEventArgs e)
{
Dgrid.DataContext = null;
vm.Students.RemoveAt(1);
vm.Students.RemoveAt(2);
Dgrid.DataContext = vm;
}
Above code will change ColumnWidth depending upon the Data.Count value and works correctly if we change number of records in Collection at runtime.
please try the next solution. As you can see I've used a proxy object to pass a main data context to each data grid cell. In addition there is a DataTrigger which works when a Visibility of a hidden column is changed and there is an attached property that helps to control the actual column width.
Here is the code:
Xaml Code
<Window x:Class="DataGridSoHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dataGridSoHelpAttempt="clr-namespace:DataGridSoHelpAttempt"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Title="MainWindow" Height="350" Width="525" x:Name="This">
<Window.DataContext>
<dataGridSoHelpAttempt:MainViewModel/>
</Window.DataContext>
<Grid x:Name="MyGrid">
<Grid.Resources>
<dataGridSoHelpAttempt:FreezableProxyClass x:Key="ProxyElement" ProxiedDataContext="{Binding Source={x:Reference This}, Path=DataContext}"/>
</Grid.Resources>
<DataGrid x:Name="MyDataGrid" ItemsSource="{Binding DataSource}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description}" Visibility="{Binding Source={StaticResource ProxyElement},
Path=ProxiedDataContext.Visibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<DataGridTextColumn Header="Comments" Binding="{Binding Comments}"/>
<DataGridTextColumn Header="Price (click to see total)" Binding="{Binding Price, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataGrid.Columns>
<DataGrid.Resources>
<Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource ProxyElement},
Path=ProxiedDataContext.Visibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="Visible">
<Setter Property="Width" Value="200"></Setter>
<Setter Property="dataGridSoHelpAttempt:DataGridAttached.ColumnActualWidth" Value="200"/>
</DataTrigger>
<DataTrigger Binding="{Binding Source={StaticResource ProxyElement},
Path=ProxiedDataContext.Visibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="Collapsed">
<Setter Property="Width" Value="400"></Setter>
<Setter Property="dataGridSoHelpAttempt:DataGridAttached.ColumnActualWidth" Value="400"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Bottom">
<Button Content="Show Description" Command="{Binding Command}"></Button>
</StackPanel>
</Grid></Window>
Attached Property Code
public class DataGridAttached
{
public static readonly DependencyProperty ColumnActualWidthProperty = DependencyProperty.RegisterAttached(
"ColumnActualWidth", typeof (double), typeof (DataGridAttached), new PropertyMetadata(default(double), ColumnActualWidthPropertyChanged));
private static void ColumnActualWidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var data = d.FindParent<DataGrid>();
var control = (d as Control);
if(data == null || control == null) return;
data.Columns.ToList().ForEach(column =>
{
var cellWidth = control.Width;
if(double.IsNaN(cellWidth) || double.IsInfinity(cellWidth)) return;
column.Width = cellWidth;
});
}
public static void SetColumnActualWidth(DependencyObject element, double value)
{
element.SetValue(ColumnActualWidthProperty, value);
}
public static double GetColumnActualWidth(DependencyObject element)
{
return (double) element.GetValue(ColumnActualWidthProperty);
}
}
View Model and Model
public class MainViewModel:BaseObservableObject
{
private Visibility _visibility;
private ICommand _command;
private Visibility _totalsVisibility;
private double _totalValue;
private double _columnWidth;
public MainViewModel()
{
Visibility = Visibility.Collapsed;
TotalsVisibility = Visibility.Collapsed;
DataSource = new ObservableCollection<BaseData>(new List<BaseData>
{
new BaseData {Name = "Uncle Vania", Description = "A.Chekhov, play", Comments = "worth reading", Price = 25},
new BaseData {Name = "Anna Karenine", Description = "L.Tolstoy, roman", Comments = "worth reading", Price = 35},
new BaseData {Name = "The Master and Margarita", Description = "M.Bulgakov, novel", Comments = "worth reading", Price = 56},
});
}
public ICommand Command
{
get
{
return _command ?? (_command = new RelayCommand(VisibilityChangingCommand));
}
}
private void VisibilityChangingCommand()
{
Visibility = Visibility == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed;
ColumnWidth = Visibility == Visibility.Visible ? 200d : 400d;
}
public ObservableCollection<BaseData> DataSource { get; set; }
public Visibility Visibility
{
get { return _visibility; }
set
{
_visibility = value;
OnPropertyChanged();
}
}
public ObservableCollection<BaseData> ColumnCollection
{
get { return DataSource; }
}
public Visibility TotalsVisibility
{
get { return _totalsVisibility; }
set
{
_totalsVisibility = value;
OnPropertyChanged();
}
}
public double TotalValue
{
get { return ColumnCollection.Sum(x => x.Price); }
}
public double ColumnWidth
{
get { return _columnWidth; }
set
{
_columnWidth = value;
OnPropertyChanged();
}
}
}
public class BaseData:BaseObservableObject
{
private string _name;
private string _description;
private string _comments;
private int _price;
public virtual string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public virtual object Description
{
get { return _description; }
set
{
_description = (string) value;
OnPropertyChanged();
}
}
public string Comments
{
get { return _comments; }
set
{
_comments = value;
OnPropertyChanged();
}
}
public int Price
{
get { return _price; }
set
{
_price = value;
OnPropertyChanged();
}
}
}
Freezable Helper
public class FreezableProxyClass : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new FreezableProxyClass();
}
public static readonly DependencyProperty ProxiedDataContextProperty = DependencyProperty.Register(
"ProxiedDataContext", typeof (object), typeof (FreezableProxyClass), new PropertyMetadata(default(object)));
public object ProxiedDataContext
{
get { return (object) GetValue(ProxiedDataContextProperty); }
set { SetValue(ProxiedDataContextProperty, value); }
}
}
Helpers
public static class VisualTreeHelperExtensions
{
public static T FindParent<T>(this DependencyObject child) where T : DependencyObject
{
while (true)
{
//get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
return parent;
child = parentObject;
}
}
}
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
public class RelayCommand : ICommand
{
private readonly Func<bool> _canExecute;
private readonly Action _execute;
public RelayCommand(Action execute)
: this(() => true, execute)
{
}
public RelayCommand(Func<bool> canExecute, Action execute)
{
_canExecute = canExecute;
_execute = execute;
}
public bool CanExecute(object parameter = null)
{
return _canExecute();
}
public void Execute(object parameter = null)
{
_execute();
}
public event EventHandler CanExecuteChanged;
}
public class RelayCommand<T> : ICommand
where T:class
{
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public RelayCommand(Action<T> execute):this(obj => true, execute)
{
}
public RelayCommand(Predicate<T> canExecute, Action<T> execute)
{
_canExecute = canExecute;
_execute = execute;
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter as T);
}
public void Execute(object parameter)
{
_execute(parameter as T);
}
public event EventHandler CanExecuteChanged;
}
It is complete test solution, you should take just the idea of how this is working. I'll be glad to help if you will have problems with the code.
Regards.

How to convert wpf AutoCompleteBox to all uppercase input

I have a AutoCompleteBox as a DataGrid column type. Like so:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Thing, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<SLToolkit:AutoCompleteBox Text="{Binding Path=Thing,
UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
However, I want to restrict the user's input to uppercase. On TextBoxes I can do so like the following, but I can't get that to work with the AutoCompleteBoxes.
<DataGridTextColumn Binding="{Binding UpdateSourceTrigger=PropertyChanged, Path=Thing}">
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="CharacterCasing" Value="Upper" />
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
I've tried this:
<SLToolkit:AutoCompleteBox Text="{Binding Path=Thing,
UpdateSourceTrigger=PropertyChanged}"
TextChanged="AutoComplete_TextChanged" />
With this:
private void AutoComplete_TextChanged(object sender, RoutedEventArgs e)
{
AutoCompleteBox box = sender as AutoCompleteBox;
if (box == null) return;
box.Text = box.Text.ToUpper();
}
That kind of works except that it writes backwards. When the user inputs a character, the cursor goes back to the start of the box so the next word is in front of the previous one. If I wrote 'example', I would see "ELPMAXE".
Any ideas?
I solved a similar problem where I only wanted entry of numbers in a textbox, so I used a behavior. If a non-number is entered, the character is deleted. I also used the interactivity library which uses the System.Windows.Interactivity.dll (just import this DLL into your project, if you don't have it its part of the blend sdk http://www.microsoft.com/en-us/download/details.aspx?id=10801).
Here is the simplified XAML:
<Window x:Class="Sample.SampleWindow"
xmlns:main="clr-namespace:MySampleApp"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Title="Sample"
Height="800"
Width="1025"
>
<Grid>
<TextBox Text="{Binding Path=Entry, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="30"
MaxLength="4"
HorizontalAlignment="Left">
<i:Interaction.Behaviors>
<main:KeyPressesWithArgsBehavior
KeyUpCommand="{Binding KeyUpFilterForUpperCaseSymbols}" />
</i:Interaction.Behaviors>
</TextBox>
</Grid>
</Window>
Uses the following Behavior class:
public class KeyPressesWithArgsBehavior : Behavior<UIElement>
{
#region KeyDown Press DependencyProperty
public ICommand KeyDownCommand
{
get { return (ICommand) GetValue(KeyDownCommandProperty); }
set { SetValue(KeyDownCommandProperty, value); }
}
public static readonly DependencyProperty KeyDownCommandProperty =
DependencyProperty.Register("KeyDownCommand", typeof (ICommand), typeof (KeyPressesWithArgsBehavior));
#endregion KeyDown Press DependencyProperty
#region KeyUp Press DependencyProperty
public ICommand KeyUpCommand
{
get { return (ICommand) GetValue(KeyUpCommandProperty); }
set { SetValue(KeyUpCommandProperty, value);}
}
public static readonly DependencyProperty KeyUpCommandProperty =
DependencyProperty.Register("KeyUpCommand", typeof(ICommand), typeof (KeyPressesWithArgsBehavior));
#endregion KeyUp Press DependencyProperty
protected override void OnAttached()
{
AssociatedObject.KeyDown += new KeyEventHandler(AssociatedUIElementKeyDown);
AssociatedObject.KeyUp += new KeyEventHandler(AssociatedUIElementKeyUp);
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.KeyDown -= new KeyEventHandler(AssociatedUIElementKeyDown);
AssociatedObject.KeyUp -= new KeyEventHandler(AssociatedUIElementKeyUp);
base.OnDetaching();
}
private void AssociatedUIElementKeyDown(object sender, KeyEventArgs e)
{
if (KeyDownCommand != null)
{
ObjectAndArgs oa = new ObjectAndArgs {Args = e, Object = AssociatedObject};
KeyDownCommand.Execute(oa);
}
}
private void AssociatedUIElementKeyUp(object sender, KeyEventArgs e)
{
if (KeyUpCommand != null)
{
KeyUpCommand.Execute(AssociatedObject);
}
}
}
Then in your View Model you can implement the command.
SampleWindowViewModel.cs:
public ICommand KeyUpFilterForUpperCaseSymbolsCommand
{
get
{
if (_keyUpFilterForUpperCaseSymbolsCommand== null)
{
_keyUpFilterForUpperCaseSymbolsCommand= new RelayCommand(KeyUpFilterForUpperCaseSymbols);
}
return _keyUpFilterForUpperCaseSymbolsCommand;
}
}
...
private void KeyUpFilterForUpperCaseSymbols(object sender)
{
TextBox tb = sender as TextBox;
if (tb is TextBox)
{
// check for a lowercase character here
// then modify tb.Text, to exclude that character.
// Example: tb.Text = oldText.Substring(0, x);
}
}

C# WPF Bindings not updating until MouseOut

I have a board with clickable labels (Grass and Unit), When I click a Grass label I it should move the Unit Label to the Grass's x and y position. It works, but kinda wrong. When I click on a label, nothing happens until I move the cursor out of the clicked label, then the wanted behaviour executes.
XAML
<local:Grass Grid.Row="9" Grid.Column="16" />
<local:Unit Grid.Row="{Binding Path=xPos, UpdateSourceTrigger=PropertyChanged}" Grid.Column="{Binding Path=yPos, UpdateSourceTrigger=PropertyChanged}" >
<local:Unit.Background>
<ImageBrush ImageSource="Images/tjej.png"/>
</local:Unit.Background>
</local:Unit>
ObjectInspector
public class ObjectInspector : INotifyPropertyChanged
{
private int _xPos = 1, _yPos = 2;
public int xPos
{
get { return _xPos; }
set
{
_xPos = value;
NotifyPropertyChanged("xPos");
}
}
public int yPos
{
get { return _yPos; }
set {
_yPos = value;
NotifyPropertyChanged("yPos");
}
}
private string _type = "none";
public string type
{
get { return _type; }
set {
_type = value;
NotifyPropertyChanged("type");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
System.Diagnostics.Debug.WriteLine("property changed");
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Grass
public class Grass : Button
{
protected override void OnClick()
{
base.OnClick();
int x = (int)this.GetValue(Grid.RowProperty);
int y = (int)this.GetValue(Grid.ColumnProperty);
string type = this.GetType().Name;
MainWindow.objectInspector.xPos = x;
MainWindow.objectInspector.yPos = y;
MainWindow.objectInspector.type = type;
}
}
MainWindow
public partial class MainWindow : Window
{
public static ObjectInspector objectInspector= new ObjectInspector();
public MainWindow()
{
InitializeComponent();
this.DataContext = objectInspector;
}
}
Any ideas?
Edit
Added MainWindow and Grass
EDIT
Try to register to the common event handler Click of buttons:
<local:Grass Grid.Row="9" Grid.Column="16" Click="ClickEventHandler" />
...
And take the grass element from the sender, in the event handler method.
Anyway, I think a better way for doing this is usin MVVM patter. You may set a GrassViewModel and UnitViewModel. Then create a DataTemplate for each one. For example:
<DataTemplate DataType="{x:Type ViewModel:UnitViewModel}">
...Visual Elements Here...
</DataTemplate>
The for showing the elements in a grid you may use a ListBox with a Grid as items panel, some like this:
<ListBox ItemsSource={Binding AllItemsCollection}>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
...rows and columns definitions here...
</Grid>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<!--HERE THE ITEMS STYLE, HERE YOU SET THE COLUMN, ROW BINDINGS-->
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Grid.Row" Value="{Binding yPos}"/>
<Setter Property="Grid.Column" Value="{Binding xPos}"/>
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Then you only need to create the AllItemsCollection in your view model with all the elements that you want. You can handler the click event using behaviors, or creating a UserControl for the grass (and controlling the click event inside):
<DataTemplate DataType="{x:Type ViewModel:UnitViewModel}">
<GrassUserControl ...Inside the grass user control you can handler the click event.../>
</DataTemplate>
Hope helps...
If your following MVVM then you can attach a property to the label as below. You can attache this behavior any control that derives from UIElement
Create a Attached property for MouseClick
public class MouseClick
{
public static readonly DependencyProperty MouseLeftClickProperty =
DependencyProperty.RegisterAttached("MouseLeftClick", typeof(ICommand), typeof(MouseClick),
new FrameworkPropertyMetadata(CallBack));
public static void SetMouseLeftClick(DependencyObject sender, ICommand value)
{
sender.SetValue(MouseLeftClickProperty, value);
}
public static ICommand GetMouseLeftClick(DependencyObject sender)
{
return sender.GetValue(MouseLeftClickProperty) as ICommand;
}
public static readonly DependencyProperty MouseEventParameterProperty =
DependencyProperty.RegisterAttached(
"MouseEventParameter",
typeof(object),
typeof(MouseClick),
new FrameworkPropertyMetadata((object)null, null));
public static object GetMouseEventParameter(DependencyObject d)
{
return d.GetValue(MouseEventParameterProperty);
}
public static void SetMouseEventParameter(DependencyObject d, object value)
{
d.SetValue(MouseEventParameterProperty, value);
}
private static void CallBack(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender != null)
{
UIElement element = sender as UIElement;
if (element != null)
{
if (e.OldValue != null)
{
element.RemoveHandler(UIElement.MouseDownEvent, new MouseButtonEventHandler(Handler));
}
if (e.NewValue != null)
{
element.AddHandler(UIElement.MouseDownEvent, new MouseButtonEventHandler(Handler), true);
}
}
}
}
private static void Handler(object sender, EventArgs e)
{
UIElement element = sender as UIElement;
if (sender != null)
{
ICommand cmd = element.GetValue(MouseLeftClickProperty) as ICommand;
if (cmd != null)
{
RoutedCommand routedCmd =cmd as RoutedCommand;
object paramenter = element.GetValue(MouseEventParameterProperty);
if (paramenter == null)
{
paramenter = element;
}
if (routedCmd != null)
{
if (routedCmd.CanExecute(paramenter, element))
{
routedCmd.Execute(paramenter, element);
}
}
else
{
if (cmd.CanExecute(paramenter))
{
cmd.Execute(paramenter);
}
}
}
}
}
}
In you Xaml attache the Command of your viewModel as below
<Label Height="30" Width="200" Margin="10" Content="Click" local:MouseClick.MouseLeftClick="{Binding Click}" />

Categories