I think this is a stupid problem but...
I have a Window class with a Treeview bind to a Observablecollection<T> foo; T is my class (in another file .cs).
My Observablecollection foo have OnPropertyChanged for add/delete element and works ok.
The problem is for my class T: it has 5 properties and one have OnPropertyChanged: when I programmatically change an element of my class T I have OnPropertyChanged in class T (obviously) but... how can I know it from Window class for do something?
You want to be able to listen to propertychange events on the items inside the observable collection?
To do this, I created an extension to ObsercableCollection that bubbles up these events to a collection level, so I can subscribe to collection.CollectionChanged (adding and removing items) and collection.ItemInCollectionChange (property of an item inside the collection has changed):
//public class ItemInCollectionChangeEventArgs
public class ItemInCollectionChangeEventArgs : EventArgs
{
public object Item { get; internal set; }
public PropertyChangedEventArgs PropertyChangedEventArgs { get; internal set; }
}
public delegate void ItemInCollectionChangeEventHandler(Object sender, ItemInCollectionChangeEventArgs e);
//TODO Make this throw a separate event for refreshing
public class ItemsChangeObservableCollection<T> :
ObservableCollection<T> where T : INotifyPropertyChanged
{
public event ItemInCollectionChangeEventHandler ItemInCollectionChangeEvent;
public ItemsChangeObservableCollection() : base()
{
}
public ItemsChangeObservableCollection(IEnumerable<T> collection) : base()
{
foreach (T item in collection)
{
Add(item);
}
}
protected virtual void OnItemInCollectionChange(ItemInCollectionChangeEventArgs e)
{
ItemInCollectionChangeEventHandler handler = ItemInCollectionChangeEvent;
if (handler != null)
{
handler(this, e);
}
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
RegisterPropertyChanged(e.NewItems);
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
UnRegisterPropertyChanged(e.OldItems);
}
else if (e.Action == NotifyCollectionChangedAction.Replace)
{
UnRegisterPropertyChanged(e.OldItems);
RegisterPropertyChanged(e.NewItems);
}
base.OnCollectionChanged(e);
}
protected override void ClearItems()
{
UnRegisterPropertyChanged(this);
base.ClearItems();
}
private void RegisterPropertyChanged(IList items)
{
foreach (INotifyPropertyChanged item in items)
{
if (item != null)
{
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
private void UnRegisterPropertyChanged(IList items)
{
foreach (INotifyPropertyChanged item in items)
{
if (item != null)
{
item.PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnItemInCollectionChange(new ItemInCollectionChangeEventArgs { Item = sender, PropertyChangedEventArgs = e });
//base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
Related
I'm making a custom behavior for Telerik's RadGridView.
When this behavior is attached and its PropertyName is set to same property as specified by
DataMemberBinding value of some of the GridViewCheckBoxColumn of the grid, then toggling the checkbox in that column will apply same checkbox state to all selected rows (but only to the same column).
That happens in the ApplyToAllSelected method, namely in gvcb.SetCurrentValue(GridViewCheckBox.IsCheckedProperty, isChecked); line. The visuals are working as expected, and all checkbox values are updated on screen.
The Problem is that the binding source is not updated for those rows. Only for the one where click happened. GridViewCheckBox.IsChecked dependency property does not seem to be bound directly to the datacontext's property, so gvcb.GetBindingExpression(GridViewCheckBox.IsChecked) returns null.
The Question: how to update source after setting checkbox state?
public sealed class CheckAllSelectedBehavior : Behavior<RadGridView>
{
public event EventHandler Toggled;
public string PropertyName { get; set; }
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.PreparingCellForEdit += this.AssociatedObject_PreparedCellForEdit;
this.AssociatedObject.CellEditEnded += this.AssociatedObject_CellEditEnded;
}
protected override void OnDetaching()
{
this.AssociatedObject.PreparingCellForEdit -= this.AssociatedObject_PreparedCellForEdit;
this.AssociatedObject.CellEditEnded -= this.AssociatedObject_CellEditEnded;
base.OnDetaching();
}
private void AssociatedObject_CellEditEnded(object sender, GridViewCellEditEndedEventArgs e)
{
if (e.Cell.Column.UniqueName == this.PropertyName && e.EditingElement is CheckBox cb)
{
cb.Checked -= this.Cb_Checked;
cb.Unchecked -= this.Cb_Unchecked;
}
}
private void AssociatedObject_PreparedCellForEdit(object sender, GridViewPreparingCellForEditEventArgs e)
{
if (e.Column.UniqueName == this.PropertyName && e.EditingElement is CheckBox cb)
{
cb.Checked += this.Cb_Checked;
cb.Unchecked += this.Cb_Unchecked;
}
}
private void Cb_Unchecked(object sender, System.Windows.RoutedEventArgs e)
{
this.ApplyToAllSelected(false);
}
private void Cb_Checked(object sender, System.Windows.RoutedEventArgs e)
{
this.ApplyToAllSelected(true);
}
private void ApplyToAllSelected(bool isChecked)
{
foreach (var item in this.AssociatedObject.SelectedItems)
{
var row = this.AssociatedObject.GetRowForItem(item);
var cell = row.GetCellFromPropertyName(this.PropertyName);
if (cell.Content is GridViewCheckBox gvcb)
{
gvcb.SetCurrentValue(GridViewCheckBox.IsCheckedProperty, isChecked);
}
}
this.Toggled?.Invoke(this, EventArgs.Empty);
}
}
Using reflection to set the value on viewmodel property seems to work. Modify the ApplyToAllSelected method like follows:
private void ApplyToAllSelected(bool isChecked)
{
foreach (var item in this.AssociatedObject.SelectedItems)
{
this.SetProperty(item, isChecked);
}
this.Toggled?.Invoke(this, EventArgs.Empty);
}
private void SetProperty(object target, bool isChecked)
{
var prop = target.GetType().GetProperty(
this.PropertyName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty);
prop.SetValue(target, isChecked);
}
I created my own DataGrid which implements a RowClick Event.
However while trying to bind a Command to it, it'll throw the exception:
{"The event \"RowClick\" on type \"ExtendedDataGrid\" has an incompatible signature. Make sure the event is public and satisfies the EventHandler delegate."}
Since I am new to MVVM my already hurts from all the Input I got in the last couple days about MVVM..Can someone hint me the (mostly) obvious error?
Thanks in advance
Here's my (testproject) code:
public class ExtendedDataGrid : DataGrid
{
public event EventHandler<DataGridRow> RowClick;
public ExtendedDataGrid()
{
this.DefaultStyleKey = typeof(DataGrid);
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
var row = (DataGridRow)element;
row.PreviewKeyDown += RowOnKeyDown;
row.MouseLeftButtonUp += RowOnMouseLeftButtonUp;
base.PrepareContainerForItemOverride(element, item);
}
protected override void ClearContainerForItemOverride(DependencyObject element, object item)
{
var row = (DataGridRow)element;
row.KeyUp -= RowOnKeyDown;
row.MouseLeftButtonUp -= RowOnMouseLeftButtonUp;
base.ClearContainerForItemOverride(element, item);
}
private void RowOnMouseLeftButtonUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
mouseButtonEventArgs.Handled = true;
this.OnRowClick((DataGridRow)sender);
}
private void RowOnKeyDown(object sender, KeyEventArgs keyEventArgs)
{
if (keyEventArgs.Key != Key.Enter)
return;
keyEventArgs.Handled = true;
this.OnRowClick((DataGridRow)sender);
}
protected virtual void OnRowClick(DataGridRow clickedRow)
{
if (null == this.RowClick)
return;
this.RowClick(this, clickedRow);
}
}
Window.xaml
<controls1:ExtendedDataGrid x:Name="extGrid">
<i:Interaction.Triggers>
<i:EventTrigger EventName="RowClick" SourceObject="{Binding ElementName=extGrid}">
<i:InvokeCommandAction Command="{Binding MyCommand}" CommandParameter="{Binding SelectedItem,ElementName=extGrid}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<controls1:ExtendedDataGrid.Items>
<TextBlock Text="Text" />
</controls1:ExtendedDataGrid.Items>
</controls1:ExtendedDataGrid>
window.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this._selectCommand = new DelegateCommand<DataGridRow>(x =>
{
});
//following works fine..
this.extGrid.RowClick += (s, e) =>
{
};
}
private DelegateCommand<DataGridRow> _selectCommand;
public ICommand MyCommand
{
get
{
return this._selectCommand;
}
}
}
DelegateCommand Implementation:
public class DelegateCommand<T> : DelegateCommand
{
public DelegateCommand(Action<T> executeHandler)
: this(null, executeHandler)
{ }
public DelegateCommand(Func<T, bool> canExecuteHandler, Action<T> executeHandler)
: base(o => null == canExecuteHandler || canExecuteHandler((T)o), o => executeHandler((T)o))
{
if (null == executeHandler)
throw new ArgumentNullException("executeHandler");
}
}
/// <summary>
/// Stellt ein standard DelegateCommand dar.
/// </summary>
public class DelegateCommand : ICommand
{
#region Events
public event EventHandler CanExecuteChanged;
#endregion
#region Variablen
private readonly Action<object> _executeHandler;
private readonly Func<object, bool> _canExecuteHandler;
private bool _isExecuting = false;
#endregion
#region Eigenschaften
public bool IsSingleExecution { get; set; }
#endregion
#region Konstruktor
public DelegateCommand(Action<object> executeHandler)
: this(null, executeHandler)
{ }
public DelegateCommand(Func<object, bool> canExecuteHandler, Action<object> executeHandler)
{
if (null == executeHandler)
throw new ArgumentNullException("executeHandler");
this._executeHandler = executeHandler;
this._canExecuteHandler = canExecuteHandler;
}
#endregion
#region Public Methoden
public virtual bool CanExecute(object parameter)
{
return (!this.IsSingleExecution || (this.IsSingleExecution && !this._isExecuting)) && (null == this._canExecuteHandler || this._canExecuteHandler(parameter));
}
public virtual void Execute(object parameter)
{
if (this.CanExecute(parameter))
{
this._isExecuting = true;
this.RaiseCanExecuteChanged();
try
{
this._executeHandler(parameter);
}
finally
{
this._isExecuting = false;
this.RaiseCanExecuteChanged();
}
}
}
public void RaiseCanExecuteChanged()
{
if (null != CanExecuteChanged)
CanExecuteChanged(this, EventArgs.Empty);
}
#endregion
The problem is coming from this line:
<i:EventTrigger EventName="RowClick" SourceObject="{Binding ElementName=extGrid}">
The EventTrigger class is expecting a routed event which uses the RoutedEventHandler delegate not the EventHandler delegate.
These are the changes you have to make in your code to make it work:
In ExtendedDataGrid:
public class ExtendedDataGrid : DataGrid
{
public static readonly RoutedEvent RowClickEvent = EventManager.RegisterRoutedEvent("RowClick",
RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ExtendedDataGrid));
public event RoutedEventHandler RowClick
{
add { AddHandler(RowClickEvent, value); }
remove { RemoveHandler(RowClickEvent, value); }
}
public ExtendedDataGrid()
{
this.DefaultStyleKey = typeof(DataGrid);
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
var row = (DataGridRow)element;
row.PreviewKeyDown += RowOnKeyDown;
row.MouseLeftButtonUp += RowOnMouseLeftButtonUp;
base.PrepareContainerForItemOverride(element, item);
}
protected override void ClearContainerForItemOverride(DependencyObject element, object item)
{
var row = (DataGridRow)element;
row.KeyUp -= RowOnKeyDown;
row.MouseLeftButtonUp -= RowOnMouseLeftButtonUp;
base.ClearContainerForItemOverride(element, item);
}
private void RowOnMouseLeftButtonUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
mouseButtonEventArgs.Handled = true;
this.OnRowClick((DataGridRow)sender);
}
private void RowOnKeyDown(object sender, KeyEventArgs keyEventArgs)
{
if (keyEventArgs.Key != Key.Enter)
return;
keyEventArgs.Handled = true;
this.OnRowClick((DataGridRow)sender);
}
protected virtual void OnRowClick(DataGridRow clickedRow)
{
var args = new RowClickRoutedEventArgs(clickedRow);
args.RoutedEvent = RowClickEvent;
RaiseEvent(args);
}
}
Here I removed the previous RowClick event and changed the OnRowClick method.
Add a new class called RowClickRoutedEventArgs:
public class RowClickRoutedEventArgs : RoutedEventArgs
{
public RowClickRoutedEventArgs(DataGridRow dataGridRow)
{
Row = dataGridRow;
}
public DataGridRow Row { get; set; }
}
I need to know, if the SelectedItems got filled when Ctrl or Shift was pressed or not. Is there an easy way (without creating a new controltemplate) to get this info? I prefer solutions without code behind.
Best regards
Yannik
You can wire up selection changed event, and check if Modifier Keys are pressed for Selection.
void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var isCtrlorShiftDown = (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl) || Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift));
if (isCtrlorShiftDown)
{
// Write your Logic Here;
}
}
I found a solution with minimal code behind.
The main concept is, that I attach to KeyDown and KeyUp events in the MainWindow and set a property "CurrentKeyboardKeyPressed" on the MainViewModel, which propagates the pressed key info to the child view models, which in turn fire a custom event with a special Selection class that has the currently pressed key info.
The posted source code is heavyli shortened and does not run at all. If somebody is interested in the working solution, just ask me and I will email it.
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private readonly MainWindowViewModel _mainWindowViewModel;
public MainWindow()
{
_mainWindowViewModel = new MainWindowViewModel();
InitializeComponent();
DataContext = _mainWindowViewModel;
}
private void MainWindow_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl)
{
_mainWindowViewModel.CurrentKeyboardKeyPressed = PressedKeyboardKey.Ctrl;
return;
}
if (e.Key == Key.LeftShift || e.Key == Key.RightShift)
{
_mainWindowViewModel.CurrentKeyboardKeyPressed = PressedKeyboardKey.Shift;
}
}
private void MainWindow_OnKeyUp(object sender, KeyEventArgs e)
{
_mainWindowViewModel.CurrentKeyboardKeyPressed = PressedKeyboardKey.None;
}
}
MainWindowViewModel.cs
public class MainWindowViewModel : ViewModelBase
{
// child view models
private readonly ObservableCollection<TTSViewModel> _ttsViewModels;
private PressedKeyboardKey _currentKeyboardKeyPressed;
public EventHandler<KeyboardKeyPressedEventArgs> CurrentKeyboardKeyPressedChanged;
public MainWindowViewModel()
{
_ttsViewModels = new ObservableCollection<TTSViewModel>();
}
public PressedKeyboardKey CurrentKeyboardKeyPressed
{
get { return _currentKeyboardKeyPressed; }
set
{
if (_currentKeyboardKeyPressed != value)
{
_currentKeyboardKeyPressed = value;
OnCurrentKeyboardKeyPressedChanged(_currentKeyboardKeyPressed);
}
}
}
// create child view models
public void PopulateTTSList(int itemsToCreated)
{
for (int i = 0; i < itemsToCreated; i++)
{
var tts = new TTSViewModel("TTS " + i);
tts.FractionSelectionChanged += OnTTSFractionSelectionChanged;
CurrentKeyboardKeyPressedChanged += tts.CurrentKeyboardKeyPressedChanged;
_ttsViewModels.Add(tts);
}
}
private void OnCurrentKeyboardKeyPressedChanged(PressedKeyboardKey currentKeyboardKeyPressed)
{
var handler = CurrentKeyboardKeyPressedChanged;
if (handler != null)
{
handler(this, new KeyboardKeyPressedEventArgs(currentKeyboardKeyPressed));
}
}
// selection changed handler for each child view model
private void OnTTSFractionSelectionChanged(object sender, ItemSelectionChangedEventArgs fractionSelectionChangedEventArgs)
{
var sendingTTS = sender as TTSViewModel;
if (fractionSelectionChangedEventArgs.Selection.PressedKeyOnSelection == PressedKeyboardKey.None)
{
// single selection action goes here
// ....
}
else
{
// multi selection action goes here
// ....
}
}
}
TTSViewModel.cs (child view model)
public class TTSViewModel : ViewModelBase
{
private readonly SmartObservableCollection<FractionViewModel> _currentlySelectedfractions;
public EventHandler<ItemSelectionChangedEventArgs> FractionSelectionChanged;
private PressedKeyboardKey _currentKeyboardKeyPressed;
public TTSViewModel()
{
_currentlySelectedfractions = new SmartObservableCollection<FractionViewModel>();
_currentlySelectedfractions.CollectionChanged += CurrentlySelectedfractionsOnCollectionChanged;
}
public void CurrentKeyboardKeyPressedChanged(object sender, KeyboardKeyPressedEventArgs currentKeyboardKeyPressedEventArgs)
{
_currentKeyboardKeyPressed = currentKeyboardKeyPressedEventArgs.CurrentKeyboardKeyPressed;
}
private void CurrentlySelectedfractionsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
if (FractionSelectionChanged != null)
{
if (notifyCollectionChangedEventArgs.Action == NotifyCollectionChangedAction.Replace && notifyCollectionChangedEventArgs.NewItems != null)
{
var removed = _oldSelectedfractions.Except(_currentlySelectedfractions);
var added = _currentlySelectedfractions.Except(_oldSelectedfractions);
var selection = new Selection<FractionViewModel>(added, removed, _currentKeyboardKeyPressed);
_oldSelectedfractions.Clear();
foreach (var currentlySelectedfraction in _currentlySelectedfractions)
{
_oldSelectedfractions.Add(currentlySelectedfraction);
}
var selectionChangedArgs = new ItemSelectionChangedEventArgs(selection);
FractionSelectionChanged(this, selectionChangedArgs);
}
}
}
}
Selection.cs
public sealed class Selection<T>
{
private readonly List<T> _added;
private readonly List<T> _removed;
private readonly PressedKeyboardKey _currentKeyboardKeyPressed;
public Selection()
{
_added = new List<T>();
_removed = new List<T>();
}
/// <summary>
/// Initializes a new instance of the <see cref="Selection{T}" /> class.
/// </summary>
/// <param name="addedItems">[NotNull]</param>
/// <param name="removedItems">[NotNull]</param>
/// <param name="currentKeyboardKeyPressed">The current keyboard key pressed.</param>
public Selection(IEnumerable<T> addedItems, IEnumerable<T> removedItems, PressedKeyboardKey currentKeyboardKeyPressed)
: this()
{
_added.AddRange(addedItems);
_removed.AddRange(removedItems);
_currentKeyboardKeyPressed = currentKeyboardKeyPressed;
}
public IReadOnlyList<T> Added
{
get { return _added; }
}
public IReadOnlyList<T> Removed
{
get { return _removed; }
}
public PressedKeyboardKey PressedKeyOnSelection
{
get { return _currentKeyboardKeyPressed; }
}
}
KeyboardKeyPressedEventArgs.cs
public sealed class KeyboardKeyPressedEventArgs : EventArgs
{
public KeyboardKeyPressedEventArgs(PressedKeyboardKey currentKeyboardKeyPressed)
{
CurrentKeyboardKeyPressed = currentKeyboardKeyPressed;
}
public PressedKeyboardKey CurrentKeyboardKeyPressed { get; private set; }
}
ItemSelectionChangedEventArgs.cs
public sealed class ItemSelectionChangedEventArgs : EventArgs
{
public ItemSelectionChangedEventArgs(Selection<FractionViewModel> newSelection)
{
Selection = newSelection;
}
public Selection<FractionViewModel> Selection { get; private set; }
}
PressedKeyboardKey.cs
public enum PressedKeyboardKey
{
None,
Ctrl,
Shift
}
OK - So I almost have this working. I just need to know who to get the usercontrol to let the viewmodel of the consuming view know there has been a change. Check this out - here is xaml from the consuming view.
<StackPanel>
<pfControls:TriChoiceUserControl Text="{Binding Path=SampleText}" State="{Binding CurrentState}"/>
</StackPanel>
Here is the viewmodel code
class MainWindowViewModel: INotifyPropertyChanged
{
private bool? currentState;
public bool? CurrentState
{
get { return currentState; }
set {
currentState = value;
OnPropertyChanged("CurrentState");
}
}
public string SampleText { get { return "Hi there"; } }
public MainWindowViewModel()
{
CurrentState = false;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Now on the initial load of the ViewModel you can see that Current state is false and indeed the control I ends up with the false check box checked (there are three check boxes, one for yes, one for no and one for na - don't ask me, that is what they told me to do). Problem is that when I check the first one (true in this case) the user control is working in that it goes and unchecks the false check box but and changes the state property but my viewmodel for the consuming view never gets notified. I feel like I am so close... Here is the code for the user control.
public partial class TriChoiceUserControl : UserControl, INotifyPropertyChanged
{
#region Fields (5)
public static readonly DependencyProperty StateProperty = DependencyProperty.Register("State", typeof(bool?), typeof(TriChoiceUserControl),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ChangeState)));
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(String), typeof(TriChoiceUserControl),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ChangeText)));
#endregion Fields
public TriChoiceUserControl()
{
InitializeComponent();
}
public bool? State
{
get
{
return (bool?)GetValue(StateProperty);
}
set
{
SetValue(StateProperty, value);
NotifyPropertyChanged("State");
}
}
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
private static void ChangeState(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as TriChoiceUserControl).UpdateCheckState((bool?)e.NewValue);
}
private static void ChangeText(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as TriChoiceUserControl).UpdateText(e.NewValue.ToString());
}
private void UpdateText(string newText)
{
label1.Content = newText;
}
private void UpdateCheckState(bool? newState)
{
if (newState != null)
{
if ((bool)newState)
{
chkYes.IsChecked = true;
chkNo.IsChecked = false;
chkNa.IsChecked = false;
}
else
{
chkYes.IsChecked = false;
chkNo.IsChecked = true;
chkNa.IsChecked = false;
}
}
else
{
chkYes.IsChecked = false;
chkNo.IsChecked = false;
chkNa.IsChecked = true;
}
State = newState;
}
private void chkYes_Checked(object sender, RoutedEventArgs e)
{
UpdateCheckState(true);
}
private void chkNo_Checked(object sender, RoutedEventArgs e)
{
UpdateCheckState(false);
}
private void chkNa_Checked(object sender, RoutedEventArgs e)
{
UpdateCheckState(null);
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Here is the XAML for the user control.
Thanks for any input.
All of this works just fine, I had lost sight of the fact that the default mode is "oneWay" on the binding - duh - I set Mode=TwoWay and no everything works. But that OK, I don't mind saying duh, it usually means I have found the answer :)
How do I capture a key down event in WPF even if my application is not focused?
For me, the best way is this:
public MainWindow()
{
InitializeComponent();
CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
if ((Keyboard.GetKeyStates(Key.W) & KeyStates.Down) > 0)
{
player1.walk();
}
}
The rendering event runs every time.
Global keyboard hook can slow down your debugging.
I prefer to use this approach:
Create KeyboardListener class
public class KeyboardListener : IDisposable
{
private readonly Thread keyboardThread;
//Here you can put those keys that you want to capture
private readonly List<KeyState> numericKeys = new List<KeyState>
{
new KeyState(Key.D0),
new KeyState(Key.D1),
new KeyState(Key.D2),
new KeyState(Key.D3),
new KeyState(Key.D4),
new KeyState(Key.D5),
new KeyState(Key.D6),
new KeyState(Key.D7),
new KeyState(Key.D8),
new KeyState(Key.D9),
new KeyState(Key.NumPad0),
new KeyState(Key.NumPad1),
new KeyState(Key.NumPad2),
new KeyState(Key.NumPad3),
new KeyState(Key.NumPad4),
new KeyState(Key.NumPad5),
new KeyState(Key.NumPad6),
new KeyState(Key.NumPad7),
new KeyState(Key.NumPad8),
new KeyState(Key.NumPad9),
new KeyState(Key.Enter)
};
private bool isRunning = true;
public KeyboardListener()
{
keyboardThread = new Thread(StartKeyboardListener) { IsBackground = true };
keyboardThread.Start();
}
private void StartKeyboardListener()
{
while (isRunning)
{
Thread.Sleep(15);
if (Application.Current != null)
{
Application.Current.Dispatcher.Invoke(() =>
{
if (Application.Current.Windows.Count > 0)
{
foreach (var keyState in numericKeys)
{
if (Keyboard.IsKeyDown(keyState.Key) && !keyState.IsPressed) //
{
keyState.IsPressed = true;
KeyboardDownEvent?.Invoke(null, new KeyEventArgs(Keyboard.PrimaryDevice, PresentationSource.FromDependencyObject(Application.Current.Windows[0]), 0, keyState.Key));
}
if (Keyboard.IsKeyUp(keyState.Key))
{
keyState.IsPressed = false;
}
}
}
});
}
}
}
public event KeyEventHandler KeyboardDownEvent;
/// <summary>
/// Состояние клавиши
/// </summary>
private class KeyState
{
public KeyState(Key key)
{
this.Key = key;
}
public Key Key { get; }
public bool IsPressed { get; set; }
}
public void Dispose()
{
isRunning = false;
Task.Run(() =>
{
if (keyboardThread != null && !keyboardThread.Join(1000))
{
keyboardThread.Abort();
}
});
}
}
Subscribe to KeyboardDownEvent in code-behind (or where you need it).
public partial class MainWindow : Window
{
private KeyboardListener listener;
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
listener = new KeyboardListener();
listener.KeyboardDownEvent += ListenerOnKeyPressed;
}
private void ListenerOnKeyPressed(object sender, KeyEventArgs e)
{
// TYPE YOUR CODE HERE
}
private void Window_OnUnloaded(object sender, RoutedEventArgs e)
{
listener.KeyboardDownEvent -= ListenerOnKeyPressed;
}
}
Done
See this questions for hooking the keyboard Using global keyboard hook (WH_KEYBOARD_LL) in WPF / C#