WPF Commands. Textbox does not handle char mapped as a hotkey - wpf

I'm stuck with wpf commands and "non-trivial" hotkeys. I want to map "+" key to some command. Within i want it to keep working with any textbox. Below is the sample
Commands.cs
public static class Commands
{
private static readonly ICommand _someCommand;
static Commands()
{
_someCommand = new RoutedCommand("cmd", typeof(Commands), new InputGestureCollection { new KeyGesture(Key.OemPlus), new KeyGesture(Key.Add) });
}
public static ICommand SomeCommand
{
get { return _someCommand; }
}
}
MainWindow.xaml
<Window x:Class="WpfHotkeysTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wpfHotkeysTest="clr-namespace:WpfHotkeysTest"
Title="MainWindow" Height="350" Width="525">
<Window.CommandBindings>
<CommandBinding Command="wpfHotkeysTest:Commands.SomeCommand" Executed="CommandBinding_OnExecuted"></CommandBinding>
</Window.CommandBindings>
<TextBox></TextBox>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void CommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e)
{
Debug.WriteLine("COMMAND! " + e.Source);
}
}
The problem is when im focused on the textbox it does not handle pressed "+" key before command is executed.
I want my key be displayed, but how do i achieve this in the best way?
UPD
I dont want to execute command if the key was handled by the text box.
I know there is property CanExecuteRoutingEventArgs.ContinueRouting. But it executes both command and textbox handling

The workaround I've found so far
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void CommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e)
{
Debug.WriteLine("COMMAND! " + e.Source);
}
private void CommandBinding_OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (IsEditableControlSelected())
{
e.ContinueRouting = true;
return;
}
e.CanExecute = true;
}
private bool IsEditableControlSelected()
{
return Keyboard.FocusedElement is TextBox;
}
}
This brings tears to my eyes, but at least this is better than nothing. Waiting for more solutions

Related

UI component does not exist in current context when code moved to class from main UI thread

I currently have working code which is implemented in MainWindow.xaml.cs that I am trying to move to a class which is giving me an error that my UI label does not exist in the current context.
Here is the code that works in the MainWindow:
public partial class MainWindow : Window
{
......
private RX consumer = new RX();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
Task backgroundDBTask = Task.Factory.StartNew(() => { Consumer(consumer);}, TaskCreationOptions.LongRunning);
}
}
public void Consumer(Consumer consumer)
{
while (true)
{
Thread.Sleep(1000);
.......
Dispatcher.BeginInvoke(new Action(() =>
{
mylbl.Content = value.ToString();
}), DispatcherPriority.Background);
}
}
Then I tried moving the code to a separate class:
public partial class MainWindow : Window
{
....
private RX consumer = new RX();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
Task backgroundDBTask = Task.Factory.StartNew(() => { consumer.ProcessMessages(); }, TaskCreationOptions.LongRunning);
}
}
}
public class RX
{
public void ProcessMessages()
{
while (true)
{
Thread.Sleep(1000);
....
var m_dispatcher = Application.Current.MainWindow;
m_dispatcher.Dispatcher.BeginInvoke(new Action(() =>
{
mylbl.Content = value.ToString();
}), DispatcherPriority.Background);
}
}
}
I'm getting the error on:
mylbl.Content = value.ToString();
from the class RX. I tried this as recommended var m_dispatcher = Application.Current.MainWindow to get to the MainWindow thread but its still giving an error.
You cannot access mylbl from other classes , other than MyWindow since it is defined there .
You can implement MVVM and bind the content property to string in view model and update the content .
or segregate your business logic to separate class and expose this to MyWindow.Xaml.cs.
You can have a public method which returns "value" in RX . and you can update your content in MyWindow.xaml.cs by accessing this method
or pass Label instance to ProcessMessage method and update the content. Of course,add a reference System.Windows.Controls in your class.
However this is not a good design . I suggest you to go through MVVM.
public void ProcessMessages(Label mylbl)
{
while (true)
{
Thread.Sleep(1000);
....
var m_dispatcher = Application.Current.MainWindow;
m_dispatcher.Dispatcher.BeginInvoke(new Action(() =>
{
mylbl.Content = value.ToString();
}), DispatcherPriority.Background);
}
}
and caller will look like this
public partial class MainWindow : Window
{
....
private RX consumer = new RX();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
Task backgroundDBTask = Task.Factory.StartNew(() => { consumer.ProcessMessages(mylbl); }, TaskCreationOptions.LongRunning);
}
}
}
As suggested by Clemens , i am updating solution in MVVM way.
XAML Part:
<Window x:Class="MvvmExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MvvmExample"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid >
<Label Content="{Binding LableContent}" Height="100" Width="500" Foreground="Red"/>
</Grid>
</Window>
I am binding LableContent string property to Content property of Label. And setting data content at the top to my View Model. Also,to bind event to command i have used interactivity dll.
ViewModel will look like this.
public class ViewModel : INotifyPropertyChanged
{
#region Constants and Enums
#endregion
#region Private and Protected Member Variables
private string _lableContent;
#endregion
#region Private and Protected Methods
private void OnLoaded(object obj)
{
Task.Factory.StartNew(() => { ProcessMessages(); }, TaskCreationOptions.LongRunning);
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region Constructors
public ViewModel()
{
LoadedCommand = new RelayCommand(OnLoaded);
}
#endregion
#region Public Properties
public event PropertyChangedEventHandler PropertyChanged;
public string LableContent
{
get
{
return _lableContent;
}
set
{
_lableContent = value;
OnPropertyChanged(nameof(LableContent));
}
}
public ICommand LoadedCommand { get; }
#endregion
#region Public Methods
public void ProcessMessages()
{
while (true)
{
Thread.Sleep(1000);
LableContent = "your value field";
}
}
#endregion
}
I have used ICommand implementation for commands .
Also i have used INotifyPropertyChanged for binding .
I assume you have the knowledge about following topics , If not there are plenty of help available in stack overflow on these
INotifyProertyChanged
Event to command binding
What is data context and how to set the data context
what is ICommand and implementing ICommand

How can i fire an event in window and handle it in UserControl? C# WPF

I'm using my usercontrol in window. I want to do something in usercontrol when window's StateChanged event fires.
I want to send statechanged event to usercontrol.
How can i do this ?
Option 1:
Define a public method in the UserControl code behind that can be called to notify the event occurance
Handle the StateChanged event in the Window code behind and call the defined method in the UserControl
Option 2:
Implement an interface for the StateChanged event and implement this interface in your Window
Implement a DependencyProperty with interface as type in the UserControl
Bind the property to your Window when you instantiate the UserControl
Register to the StateChanged event on property changed in the code behind of UserControl
Some code for demonstration how to implement and use option 2:
public interface IStateChanged
{
event EventHandler StateChanged;
}
public partial class MainWindow : Window, IStateChanged
{
public MainWindow()
{
InitializeComponent();
}
}
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public IStateChanged StateChangedHost
{
get { return (IStateChanged)GetValue(StateChangedHostProperty); }
set { SetValue(StateChangedHostProperty, value); }
}
// Using a DependencyProperty as the backing store for StateChangedHost. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StateChangedHostProperty =
DependencyProperty.Register("StateChangedHost", typeof(IStateChanged), typeof(MyUserControl), new FrameworkPropertyMetadata(null, StateChangedHost_PropertyChanged));
private static void StateChangedHost_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var self = d as MyUserControl;
if (e.OldValue != null)
{
((IStateChanged)e.OldValue).StateChanged -= self.NotifyStateChanged;
}
if (e.NewValue != null)
{
((IStateChanged)e.NewValue).StateChanged += self.NotifyStateChanged;
}
}
private void NotifyStateChanged(object sender, EventArgs e)
{
// implementation logic on StateChanged event
}
}
<Window [...]>
<Grid>
<local:MyUserControl StateChangedHost="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"/>
</Grid>
</Window>
Add a method in the User Control to notify it
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void MainWindow_StateChanged(object sender, EventArgs e)
{
myUserControl.StateChanged();
}
}
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public StateChanged()
{
...
}
}
<Window [...]>
<Grid>
<local:MyUserControl x:Name="myUserControl"/>
</Grid>
</Window>
This should work
public partial class MyUserControl : UserControl
{
public MyUserControl ()
{
InitializeComponent();
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(Window.WindowStateProperty, typeof(Window));
dpd.AddValueChanged(Application.Current.MainWindow, (s, e) =>
{
//your code
});
}
}
Basically it tells the user control to observe WindowsStateProperty and any time that state changes it will run
I think, that's not the right approach.
my code would look like this:
public class MyUserControl : UserControl{
event EventHandler ParentWindowStateChanged;
public void RaiseParentWindowStateChanged(Window sender){
this.ParentWindowStateChanged?.Invoke(sender, EventArgs.Empty);
}
}
on Window.StateChanged you can call myUserControl. RaiseParentWindowStateChanged(this).
in constructor of UserControl you can add handler for event ParentWindowStateChanged like
MyUserControl(){
this. ParentWindowStateChanged += (sender, args) => {
// do something
};
Regards
Steffen

wpf datagrid icollectionview sorting BUG?

maybe someone can help me? I have the following scenario:
A simple view:
<Window x:Class="DataGridSortBug.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<StackPanel DockPanel.Dock="Top">
<Button Click="Button_Click">Refresh</Button>
</StackPanel>
<DataGrid ItemsSource="{Binding View}" />
</DockPanel>
</Window>
The code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
public class TestItem
{
private int _sequence;
public int Sequence
{
get { return _sequence; }
}
public TestItem(int sequence)
{
_sequence = sequence;
}
}
public class ViewModel
{
ObservableCollection<TestItem> _collection;
private ICollectionView _view;
public ICollectionView View
{
get { return _view; }
}
public ViewModel()
{
_collection = new ObservableCollection<TestItem>();
_collection.Add(new TestItem(5));
_collection.Add(new TestItem(2));
_collection.Add(new TestItem(4));
_collection.Add(new TestItem(3));
_collection.Add(new TestItem(1));
_view = CollectionViewSource.GetDefaultView(_collection);
_view.SortDescriptions.Add(new SortDescription("Sequence", ListSortDirection.Ascending));
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
DataContext = new ViewModel();
}
}
After the program startup the datagrid contains (as expected):
1
2
3
4
5
After click on the button:
5
2
4
3
1
But I really can't understand why. Am I doing something wrong or is this a bug? And if this is a bug is there a workaround?
I just ran into this bug. (Or at least I presume it is a bug).
When debugging, you can see that the SortDescriptions collection gets cleared after assigning the ViewModel to the DataContext.
As a work around, I removed the SortDescriptions from the CTOR of the ViewModel and put them within a public method which I then call after assigning the ViewModel to the DataContext.
private void Button_Click(object sender, RoutedEventArgs e)
{
var model = new ViewModel();
DataContext = model; // SortDescriptions collection is cleared here.
model.AddSortDescriptions();
model.View.Refresh();
}
It is far from ideal, however this seems to be the only workaround I could find.
Try calling
_view.Refresh();
after adding the SortDescription.
Your TestItem is not implementing the IComparable interface so it is not sure of what to compare your objects by.
MSDN IComparable
Basically you need to add this to your class below.
public class TestItem : IComparable
{
private int _sequence;
public int Sequence
{
get { return _sequence; }
}
public TestItem(int sequence)
{
_sequence = sequence;
}
public int CompareTo(object obj)
{
if (obj == null)
return 1;
// put comparison logic here
}

Binding UpdateSourceTrigger=Explicit, updates source at program startup

I have following code:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<TextBox Text="{Binding Path=Name,
Mode=OneWayToSource,
UpdateSourceTrigger=Explicit,
FallbackValue=default text}"
KeyUp="TextBox_KeyUp"
x:Name="textBox1"/>
</Grid>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void TextBox_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
BindingExpression exp = this.textBox1.GetBindingExpression(TextBox.TextProperty);
exp.UpdateSource();
}
}
}
public class ViewModel
{
public string Name
{
set
{
Debug.WriteLine("setting name: " + value);
}
}
}
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Window1 window = new Window1();
window.DataContext = new ViewModel();
window.Show();
}
}
I want to update source only when "Enter" key is pressed in textbox. This works fine. However binding updates source at program startup. How can I avoid this? Am I missing something?
The problem is, that DataBinding is resolved on the call of Show (and on InitializeComponent, but that is not important for you, because at that point your DataContext is not set yet). I don't think you can prevent that, but I have an idea for a workaround:
Do not set the DataContext before you call Show(). You can achieve this (for example) like this:
public partial class Window1 : Window
{
public Window1(object dataContext)
{
InitializeComponent();
this.Loaded += (sender, e) =>
{
DataContext = dataContext;
};
}
}
and:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Window1 window = new Window1(new ViewModel());
window.Show();
}
Change your Binding Mode to Default
<TextBox Text="{Binding Path=Name,
Mode=Default,
UpdateSourceTrigger=Explicit,
FallbackValue=default text}"
KeyUp="TextBox_KeyUp"
x:Name="textBox1"/>

INotifyPropertyChanged not working on ObservableCollection property

I have a class called IssuesView which implements INotifyPropertyChanged. This class holds an ObservableCollection<Issue> and exposes it as a DependencyProperty called Issues for consumption by Bindings. It is defined as below -
public class IssuesView : DependencyObject, INotifyPropertyChanged
{
public Issues Issues
{
get { return (Issues)GetValue(IssuesProperty); }
set
{
SetValue(IssuesProperty, value);
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Issues"));
}
}
}
// Using a DependencyProperty as the backing store for Issues. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IssuesProperty =
DependencyProperty.Register("Issues", typeof(Issues), typeof(IssuesView), new UIPropertyMetadata(null));
public IssuesView()
{
Refresh();
}
public void Refresh()
{
this.Issues = new Issues();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
I have a test page declared like this -
<Page x:Class="Tracker.Pages.DEMO"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cont="clr-namespace:Tracker.Controls"
Title="DEMO">
<StackPanel>
<Button Click="Button_Click">Change</Button>
<cont:IssueTimeline IssuesForTimeline="{Binding Source={StaticResource issuesView},Path = Issues}"/>
</StackPanel>
The IssuesView class is defined in Application.Resources.
Now in the event hadnler for the button i have this code -
private void Button_Click(object sender, RoutedEventArgs e)
{
IssuesView iv = Application.Current.FindResource("issuesView") as IssuesView;
if (!once)
{
foreach (Issue i in iv.Issues)
{
i.DormantFor = new TimeSpan(30, 0, 0, 0);
i.AssignedUserID = 12;
i.Name = "MyName";
i.Priority = Issue.Priorities.Critical;
i.Status = Issue.Statuses.New;
i.Summary = "NewSummary";
}
once = true;
}
else
{
iv.Refresh();
}
once is a simple boolean to test mutation of the collection versus repopulation.
The first button click alters the collection's items and the UI is updated properly since the items implement INotifyPropertyChanged but the second click repopulates the collection but does not update the UI even though the event is not null and fires properly.
Why does the UI not update on the second click? How can i make it so that repopulating the collection will cause a UI update?
You really need to simplify your repro. I can see several things wrong with it, but cannot help to solve your problem without seeing all of it. Here is my simple repro, which works just fine.
Window1.xaml:
<Window x:Name="_root" x:Class="CollectionRepro.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel DataContext="{Binding ElementName=_root}">
<ItemsControl ItemsSource="{Binding Issues}"/>
<Button x:Name="_addButton">Add</Button>
<Button x:Name="_resetButton">Reset</Button>
</StackPanel>
</Window>
Window1.xaml.cs:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
namespace CollectionRepro
{
public partial class Window1 : Window
{
public static readonly DependencyProperty IssuesProperty = DependencyProperty.Register("Issues",
typeof(ICollection<string>),
typeof(Window1));
public ICollection<string> Issues
{
get { return (ICollection<string>)GetValue(IssuesProperty); }
set { SetValue(IssuesProperty, value); }
}
public Window1()
{
InitializeComponent();
Reset();
_addButton.Click +=new RoutedEventHandler(_addButton_Click);
_resetButton.Click += new RoutedEventHandler(_resetButton_Click);
}
void _resetButton_Click(object sender, RoutedEventArgs e)
{
Reset();
}
void _addButton_Click(object sender, RoutedEventArgs e)
{
Issues.Add("Another issue");
}
private void Reset()
{
Issues = new ObservableCollection<string>();
}
}
}
First: there's no reason to implement INotifyProperty changed for DependencyProperties. DependencyProperties know when they change.
Second: I don't see an ObservableCollection in your code.
Third: it's not entirely clear to me (from the code you posted) where the issues you modify in the first click come from. I assume from another action, not posted here.
Am I correct if I assume that you want to clear the issues list with the second click (since I don't know what the Issues constructor does)?

Resources