How to make Silverlight TextBox.TextChanged event fire synchronously? - c#

Platform: Silverlight 4 / .NET 4
Description:
I have a ComboBox, a Textbox and a NumericUpDown.
<StackPanel Margin="10">
<ComboBox SelectionChanged="cmbChanged" Margin="3">
<ComboBoxItem Content="A" />
<ComboBoxItem Content="B" />
</ComboBox>
<TextBox x:Name="txt" TextChanged="txtChanged" Margin="3"/>
<CheckBox x:Name="chk" Checked="chkChecked" Unchecked="chkChecked" Content="Check box" Margin="3"/>
<ListBox x:Name="lst" Height="100" Margin="3"/>
</StackPanel>
A list is for debugging purposes.
Repro:
Note that there are event handlers for TextBox.TextChanged, CheckBox.Checked, CheckBox.Unchecked and ComboBox.SelectionChanged.
Here are the handlers:
private void cmbChanged(object sender, SelectionChangedEventArgs e)
{
lst.Items.Clear();
txt.Text = (sender as ComboBox).SelectedIndex.ToString();
chk.IsChecked = !chk.IsChecked;
}
private void txtChanged(object sender, TextChangedEventArgs e)
{
lst.Items.Add("Text Changed");
}
private void chkChecked(object sender, RoutedEventArgs e)
{
bool? chk = (sender as CheckBox).IsChecked;
lst.Items.Add("CheckBox Changed to " + chk);
}
Problem:
In the combobox's event handler, I set the text in the textbox BEFORE setting the check state of the checkbox. However, if you take a look at the picture below, you will see that the event handler for the CheckBox.Checked gets called before TextBox.TextChanged.
The problem is obviously in the asynchronous execution of the TextBox.TextChanged event, as stated in the MSDN definition:
The TextChanged event is asynchronous. The event cannot be canceled.
I really need these event handlers to execute exactly in the order they are changed.
Question:
Is there any simple way to achieve what I need?

Perhaps it might be better to call your piece of business logic directly, instead of relying on the event handlers. You can always remove and add the EventHandler in case you don't want to hit that trigger in your particular situation:
//Remove event handlers:
CheckBox.Checked -= chkChecked;
TextBox.TextChanged -= txtChanged;
//Call your business stuff here
//You can also 'safely' set values on your textbox and
//checkbox since they wont trigger events now.
//Re-add event handlers, so they react on user input again:
CheckBox.Checked += chkChecked;
TextBox.TextChanged += txtChanged;

I'm not a Silverlight guy so these are suggestions:
Use the KeyDown/KeyUp events to capture changes to your text box.
Use the FocusManager to manage synchronous GotFocus/LostFocus events and check for changes there.

Related

SelectionChanged fired also on nested controls?

Sorry for misleading title, I'll try to explain better.
I've a TabControl like this:
<dragablz:TabablzControl SelectionChanged="MainTabs_SelectionChanged" x:Name="MainTabs">
where inside I've different TabItems, I need to fire the event MainTabs_SelectionChanged each time the user change the TabItem, this working but the event is fired also when the selection of a combobox, available inside the tabitem, change.
This is the ComboBox:
<ComboBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding Groups}"
Margin="8,0,8,16" DisplayMemberPath="Name" SelectedItem="{Binding SelectedGroup}" />
why happen this?
why happen this?
Because SelectionChanged is a routed event.
Routed Events Overview: https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/routed-events-overview
You could use the OriginalSource property to determine whether a tab was selected:
private void MainTabs_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.OriginalSource == MainTabs)
{
//do your thing
}
}

Prevent SelectionChanged event during DragDrop WPF

I'm working on a WPF application with MVVM pattern using Telerik controls.
Functionality:
I'm using telerik:RadListBox for which a collection is bind at runtime. I can ReOrder the items in the RadListBox.
Issue:
When i DragDrop items within RadListBox after DragLeave event the SelectionChanged event gets fired.
XAML:
<telerik:RadListBox x:Name="lstMarketSeries" ItemsSource="{Binding MarketSeriesCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True, ValidatesOnDataErrors=True}" ItemContainerStyle="{StaticResource DraggableListBoxItem}" DragLeave="lstMarketSeries_DragLeave" SelectionMode="Extended" telerik:StyleManager.Theme="Windows8" SelectionChanged="MarketSeriesCommit_SelectionChanged">
</telerik:RadListBox>
XAML.cs:
private void MarketSeriesCommit_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
private void lstMarketSeries_DragLeave(object sender, DragEventArgs e)
{
}
Is there any way that i can restrict the SelectionChanged event getting fired after DragLeave event?
I don't think that this is a good idea to prevent the selection event hadler to fire. But you can try to add the flag IsInDrag(boolean) and manage this during the darag/drop action(true on start dragging and false when you enter the final selection changed handler), in addition when you entering the SelectionChanged event handler and the flag is true you set this false and leave the handler(in addition you can set the handle property of the SelectionChangedEventArgs to true) that's all.
Regards.

Listen for generic events

I have a function I would like to run on after update of a lot of different text boxes, is it possible to listen for a generic after update event rather than the specific events?
So rather than 100 individual calls to the function, just one listener?
Edit: It would appear we are using a combination of MVVM and traditional code behind.
Here is one of the textboxes:
<TextBox Text="{Binding APhaseFrom}" x:Name="txtFromWhereA" TabIndex="26" HorizontalContentAlignment="Center" HorizontalAlignment="Left" Height="48" TextWrapping="NoWrap" VerticalAlignment="Top" Width="261" FontSize="26" FontWeight="Bold" BorderBrush="Black" BorderThickness="1" Margin="289,656,0,0" GotMouseCapture="txtFromWhereA_GotMouseCapture" GotFocus="txtFromWhereA_GotFocus" Grid.Row="3" />
The code from the view Model:
public string APhaseFrom
{
get { return new string((char[])_f.Rows[1].GetValue("Alpha09")); }
set
{
if (value.Length <= 35)
{
_f.Rows[1].SetValue("Alpha09", value);
}
else
{
MessageBox.Show("Error: String length Longer than 35 Characters.");
}
}
}
We also are using some commands for other processes:
public ICommand Updatesql
{
get;
internal set;
}
private void CreateUpdatesql()
{
Updatesql = new RelayCommand(UpdatesqlExecute);
}
private void UpdatesqlExecute()
{
_f.Update();
}
Should I be using commands or just link the events to functions in the viewmodel?
Since you are using WPF, and if I understand your problem correctly, then the RoutedEvents that WPF uses may help you here. Essentially, events like the LostFocus event of a TextBox will bubble up your UI hierarchy and can be handled by a common parent control. Consider this snippet of XAML and codebehind:
<StackPanel TextBox.LostFocus="TextBoxLostFocus">
<TextBox></TextBox>
<TextBox></TextBox>
<TextBox></TextBox>
</StackPanel>
Codebehind:
private void TextBoxLostFocus(object sender, RoutedEventArgs e)
{
MessageBox.Show("Lost Focus!");
}
You will find that the event handler is called for any of the three textboxes when focus is lost. The sender parameter or e.Source can be used to find the textbox that fired the event.
This pattern holds true for any RoutedEvent, so things like Button.Click or TextBox.TextChanged and many more can be caught in this manner.
Really and truthfully you should be using a single design pattern... ie MVVM when writing WPF applications, each textbox would be bound to a property which implements the INotifyPropertyChange interface.
In the setter of each property you would essentially update the value, fire a property changed event and then either make a call to your method or simply add an event handler on the view model for the PropertyChanged event.
Also... MessageBox.Show is a bad idea in your view models, its hard to unit test it.
Update
I removed my previous ideas because I now understand more clearly what you are looking for.
But you definitely need to use the LostFocus event.
<TextBox Text="{Binding APhaseFrom}" x:Name="txtFromWhereA" LostFocus="OnLostFocus" />

Wpf event not bubbling

Here is my XAML:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="844.025" Width="678" MouseUp="somethingClicked">
<Grid MouseUp="somethingClicked">
<StackPanel MouseUp="somethingClicked" Margin="0,0,10,0">
<Button x:Name="btnClickMe" Content="Click Me!" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="101,22,0,0" MouseUp="somethingClicked"/>
<CheckBox x:Name="chkhandle" Content="CheckBox" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="241,28,0,0" RenderTransformOrigin="-0.588,1.188"/>
<ListBox x:Name="lstEvents" HorizontalAlignment="Left" Height="604" VerticalAlignment="Top" Width="416" Margin="29,66,0,0"/>
</StackPanel>
</Grid>
And here is the C# Code:
namespace WpfApplication4
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
protected int eventCounter = 0;
public MainWindow()
{
InitializeComponent();
}
private void somethingClicked(object sender, RoutedEventArgs e)
{
eventCounter++;
String message = "#" + eventCounter.ToString() + ":\r\n" +
" Sender: " + sender.ToString() + ":\r\n" +
" Source: " + e.Source + ":\r\n" +
" Original Source: " + e.OriginalSource;
lstEvents.Items.Add(message);
e.Handled = (bool) chkhandle.IsChecked;
if (e.Handled)
lstEvents.Items.Add("Completed");
}
}
}
I have the following issues with this example:
1)The MouseUp event is not fired on clicking the button.
2)The event doesn't bubble up. Clicking somewhere on the form displays:
Sender:WpfApplication4.MainWindow:
Source:WpfApplication4.MainWindow:
Original Source: System.Windows.Controls.Border.
If I understand rightly, when button is clicked, first it should be executed at Window level (which it does now), then Grid, then stack and finally text label. Is the code wrong or is my understanding of the concept faulty?
The MouseUp event is not fired on clicking the button.
Because the first fires is an event at the Button.Click, and when it works, it conflicts with the event MouseUp. Quote from here:
ButtonBase inherits from UIElement, a Button will also have access to all of the mouse button events defined for UIElement. Because the Button does something in response to button presses, it swallows the bubbling events (e.g. MouseLeftButtonDown and MouseDown). You can still detect these lower level button press events by adding handlers for the tunneling events (e.g. PreviewMouseLeftButtonDown and PreviewMouseDown).
Try to replace the Button on Label, and you'll get the desired result:
Try handling the PreviewMouseDown event instead. You can still
attach that from XAML. In your handler
Attach the event handler in code instead. Use the signature of
AddHandler
.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Grid1.MouseUp += new MouseButtonEventHandler(Grid1_MouseUp);
}
private void Grid1_MouseUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Mouseup");
}
Microsoft wrote a very nice explanation Routed Events Overview
exactly the same thing will happen with MouseUpand PreviewMouseUp events
in your case the e.Handled = (bool) chkhandle.IsChecked; stops the routing of the event.
if you want to debug the events you can use Snoop it will illustrate very nicely which events happened on which objects and who handled them.
There is an override available to handle events, even though they were marked as handled. It requires that you add your handler through code as the following:
MainWindow.AddHander(UIElement.MouseUpEvent, new MouseButtonEventHandler(button1_MouseUp), true);
That last parameter specifies whether you want to accept events that were handled already or not. If you add that handler to your main window, you'll notice that the routed MouseUp events from your button are indeed bubbling up, (but their e.Handled indicates that they were already handled).

Click Event not fired in WPF Datagrid in unselected row

I have a DataGrid (WPF 4) like this:
<DataGrid Margin="0,0,0,5" VerticalAlignment="Top" Height="192"
BorderBrush="#aaa" Background="White"
HorizontalAlignment="Left"
ItemsSource="{Binding Namen, Mode=OneWay}"
ScrollViewer.VerticalScrollBarVisibility="Visible"
AutoGenerateColumns="False"
ColumnHeaderHeight="24"
SelectionChanged="DataGridAuslaendischeAlteNamen_SelectionChanged">
<DataGrid.Columns>
<DataGridTextColumn Width="*" Header="Namenseintrag" Binding="{Binding DisplayName, Mode=OneWay}" />
<DataGridTextColumn Width="75" Header="gültig von" Binding="{Binding GueltigAb, StringFormat=d, Mode=OneWay}" />
<DataGridTextColumn Width="75" Header="gültig bis" Binding="{Binding GueltigBis, StringFormat=d., Mode=OneWay}" />
<DataGridTemplateColumn Width="20" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Style="{DynamicResource CaratRemoveButton}"
Click="Button_Click" CommandParameter="{Binding}"
PreviewMouseDown="Button_PreviewMouseDown"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The issue I experience is, that a button of the DataGridTemplateColumn does not fire the click-event, if its row is not selected. So I have to click a button twice, one time to select its row and then to raise the click event.
I read about similiar problems with checkbox-columns, but there the suggestion was obviously to use a template column.
I tested to use the PreviewMouseDown-Event of the button, which works, but is not what I want, since then the button does not follow its usual grpahical click behaviour.
What am I missing here? How can I get that click event by just clicking once, regardless of whether the row was selected or not?
basically, you have no solution except using a TemplateColumn and managing every single mouseEvent yourself.
Explanation :
click = mouseDown + MouseUp, right.
so your button needs to be able to get the mouseDown + the MouseUp event.
BUT...
by default, wpf's DataGrid has its rows handling the MouseDown Event so as to select the one you do the mouseDown on (to confirm: mouseDown on a cell and hold the mouseButton down, you'll see that the row is selected BEFORE you release the button).
So basically, the MouseDownEvent is Handled before it reaches the button, preventing you to be able to use the Click event on the button
Microsoft tell us in their doc that in such cases, we should turn to the Preview kind of event, but this cannot apply to click event since there is no way you could have a previewClickEvent
So the only solution I can see for you is to listen to both PreviewMouseDown and PreviewMouseUp on your button and simulating a click from them yourself
something a bit like this :
Button myButton = new Button();
bool mouseLeftButtonDownOnMyButton;
myButton.PreviewMouseLeftButtonDown += (s, e) => { mouseLeftButtonDownOnMyButton = true; };
myButton.PreviewMouseLeftButtonUp += (s, e) => {
if (mouseLeftButtonDownOnMyButton)
myButton.RaiseEvent( new RoutedEventArgs(Button.ClickEvent,myButton));
mouseLeftButtonDownOnMyButton = false;
};
myButton.Click += myButtonCLickHandler;
(of course, you'd need to translate that in your xaml template)
NB: this is not complete, you should also take care of the cases when the user does a mouseDown on the button but moves the mouse out of the button before doing the mouseup (in wich case you should reset the mouseLeftButtonDownOnMyButton flag). The best way would probably be to reset the flag in a general mouseUpEvent (on the window level for instance) instead of in the button's one.
Edit: the above code lets you manage the Click event as well and have only one code for both the real and simulated click events (hence the use of the RaiseEvent method), but if you don't need this, you could directly put your code in the PreviewMouseUp section as well of course.
Another solution would be to create your own button. One advantage of this: you do not have to hook up the events for every button.
public class FireOnPreviewButton : Button
{
#region Constructor
public FireOnPreviewButton()
{
PreviewMouseLeftButtonDown += OnLeftMouseButtonDownPreview;
}
#endregion
#region Event Handler
private void OnLeftMouseButtonDownPreview(object sender, MouseButtonEventArgs e)
{
// Prevent the event from going further
e.Handled = true;
// Invoke a click event on the button
var peer = new ButtonAutomationPeer(this);
var invokeProv = peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider;
if (invokeProv != null) invokeProv.Invoke();
}
#endregion
}
I realize this question is old but I'm on a 10 year contract that uses WPF and XAML and I ran into the same issue where users had to click a row in the datagrid twice to activate the mouse button down event. This is because the datagrid consumes the first click to select the row and the second click to trigger the event. I found a much simpler solution that works for my application:
PreviewMouseLeftButtonDown="MainDataGrid_OnMouseLeftButtonDown"
This triggers the mouse left button down event on the first click. This is how it looks in the datagrid setup:
<DataGrid Name="MainDataGrid" IsSynchronizedWithCurrentItem="True" SelectionMode="Extended" SelectionUnit="FullRow"
...removed other options for brevity
PreviewMouseLeftButtonDown="MainDataGrid_OnMouseLeftButtonDown"
...removed other options for brevity
RowHeight="30" BorderThickness="0" Background="#99F0F0F0" Grid.Column="{Binding GridColumn, Mode=TwoWay}" Grid.Row="1">
I used that instead of MouseLeftButtonDown. Cheers.
I had the same issue - I was using Image on Template Column and had event on MouseDown, but I had to click twice. So I called event handler on constructor and it worked for me.
You can try this:
constructor() {
datagridname.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click), true);
}
This may not be an option for you, but setting the to IsReadonly=true solved that issue for me.
It's too late but not for others.
I have been facing the same issue and spent almost 6 hours to find a workaround. I don't want someone else to waste their time again. After seeing all the possible answers. This is how I fixed it:
I bounded a method to the main/parent DataGrid's Loaded event. Method definition is:
private void FrameworkElement_OnLoaded(object sender, RoutedEventArgs e)
{
var grid = sender as DataGrid;
if (grid != null && grid.CurrentItem == null && grid.SelectedItem != null)
{
grid.CurrentItem = grid.SelectedItem;
}
else if (grid != null) //Fix when IsSynchronizedWithCurrentItem=True
{
var selectedItem = grid.SelectedItem;
grid.CurrentItem = null;
grid.SelectedItem = null;
grid.CurrentItem = selectedItem;
grid.SelectedItem = selectedItem;
}
}

Categories

Resources