Two way binding of a textbox to a slider in WPF - wpf

I am having the hardest time figuring out a way to solve a problem I am having with databinding on a slider and a textbox.
The setup:
the current value of the slider is displayed inside of the textbox. When the user drags the slider the value is reflected inside the textbox. The user can choose to drag the slider and release to the value he chooses, click anywhere on the slider track to set the value or enter the value manually in the texbox. In the last case, the value entered in the textbox should update the slider position.
The texbox is two way bound to a datacontext property while the slider is one way bound to the same property. When the user slides or click on the slider tracker, I use the dragcompleted event of the slider to notify the datacontext of the modification. When the user clicks on the tracker on the other hand I use the OnValueChanged event of the slider to notify the datacontext (and use a flag to ensure the OnValueChanged was not triggered by a slider move)
The problem: The OnValueChanged event fires even when initializing the slider value with the binding value so I cannot figure out whether the value is actually coming from the user or the binding.
Could you please suggest maybe and alternative way to do the binding to ensure we can distinguish between user update and binding udpates for the slider?
Thnak you!
UPDATE Sorry I forgot to mention why I am not binding directly both slider and textbox two ways like the below answers suggest. The update to the data context value is supposed to trigger a call to a backend server and retrieve data from a database. The problem is that when the user drags the slider it constantly fires updates. I go around the problem by only relying to the actual onValueChanged event to call the DoWhatever method. I hope that's a bit clearer. Sorry for omitting this...
I quickly put together the sample below for you to give it a try.
The xaml:
<Window x:Class="SliderIssue.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">
<Grid HorizontalAlignment="Center"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Slider Name="slider" VerticalAlignment="Top"
ValueChanged="slider_ValueChanged"
Thumb.DragStarted="slider_DragStarted"
Thumb.DragCompleted="slider_DragCompleted"
Value="{Binding Count}"
Width="200"
Minimum="0"
Maximum="100"/>
<TextBox VerticalAlignment="Top"
HorizontalAlignment="Left"
Grid.Column="1"
Width="100"
Text="{Binding Count,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
Height="25"/>
</Grid>
The code behind:
using System.Windows;
namespace SliderIssue
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private bool _dragStarted;
public MainWindow()
{
InitializeComponent();
var item = new Item();
DataContext = item;
}
private void slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (!_dragStarted)
{
var item = (Item)DataContext;
item.DoWhatever(e.NewValue);
}
}
private void slider_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
_dragStarted = true;
}
private void slider_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
_dragStarted = false;
var item = (Item) DataContext;
item.DoWhatever(slider.Value);
}
}
}
A simple data class:
using System.ComponentModel;
namespace SliderIssue
{
public class Item : INotifyPropertyChanged
{
private int _count = 50;
public int Count
{
get { return _count; }
set
{
if (_count != value)
{
_count = value;
DoWhatever(_count);
OnPropertyChanged("Count");
}
}
}
public void DoWhatever(double value)
{
//do something with value
//and blablabla
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}

UPDATE
OK, now I see why you were trying to do it like that. I have a couple of suggestions that may help.
My first one is a bit more opinionated, but I offer it nonetheless. If the problem you are trying to solve is throttling requests to a back-end database, I would contend that your ViewModel need not concern itself with that. I would push that down a layer into an object that is making the call to the back-end based on the updated value passed down from the ViewModel.
You could create a poor-man's throttling attempt by recording DateTimeOffset.Now each time a call is made to the method to query the back-end DB. Compare that value to the last value recorded. If the TimeSpan between falls beneath your minimum threshold, update the value to which it was compared, and ignore the request.
You could do a similar thing with a timer and resetting the timer each time a request is made, but that is messier.
When the call returns from the back-end, this layer raises an event which the ViewModel handles and does whatever it needs to do with the data returned.
As another suggestion, I would also check out what the ReactiveExtensions give you. It takes a bit to kind of wrap your brain around how they work, but you could create an Observable from a stream of events, and then use the Throttle() method to return another Observable. You subscribe to that Observable and perform your call there. It would take more re-thinking the design and architecture of your software, but it is intriguing.
Paul Betts created an entire MVVM framework based around Rx called ReactiveUI. I first learned about throttling Observables in one of his blog posts here.
Good luck!
ORIGINAL POST
If I understand your problem correctly, it sounds like you would like both the Slider and the TextBox to reflect the same property of the DataContext (normally, a ViewModel). It looks like you are trying to duplicate what the binding mechanism of WPF gives you. I was able to get a quick prototype of this working. Here's the code I used.
For the view, I just created a new window with this as the content of the Window.
<StackPanel>
<Slider Value="{Binding TheValue}" Margin="16" />
<TextBox Text="{Binding TheValue}" Margin="16" />
</StackPanel>
Notice that both the Slider and the TextBox are bound to the same (cleverly-named) value of the DataContext. When the user enters a new value into the TextBox, the value will change, and the property change notification (in the ViewModel) will cause the slider to update its value automatically.
Here is the code for the ViewModel (i.e., the DataContext of the View).
class TextySlideyViewModel : ViewModelBase
{
private double _theValue;
public double TheValue
{
get { return _theValue; }
set
{
if(_theValue == value)
return;
_theValue = value;
OnPropertyChanged("TheValue");
}
}
}
My ViewModel is derived from a ViewModelBase class which implements the INotifyPropertyChanged interface. The OnPropertyChanged() method is defined in the base class which merely raises the event for the property whose name is passed as the parameter.
Lastly, I created the View and assigned a new instance of the ViewModel as its DataContext (I did this directly in the App's OnStartup() method for this example).
I hope this helps get you going in the right direction.

UPDATE:
Along the lines with Eric, but as a separate suggestion of operation.
Bind both controls to Count as two way as I suggested below.
Create a timer which fires off every second that checks two variables.
(Timer Check #1) Checks to see if a database request is ongoing (such as a Boolean flag). If it is true, it does nothing. If there is no operation (false), it goes to step 4.
(Timer Check #2) It checks to see if count is changed. If count has changed it sets the data request ongoing flag (as found/used in step 3) and initiates an async database call and exits.
(Database Action Call) Gets the database data and updates the VM accordingly. It sets the data request ongoing flag to false which allows the timer check to start a new request if count is changed.
That way you can manage the updates even if a user goes crazy with the slider.
I believe you may have over thought this. Remove all the events off of the slider and the textbox. If the first value (set programmatically) should not call your DoWhatever method, then put in a check in that code to skip the first initialization....
I recommend that you make the slider bind to Count as a TwoWay mode and have the Count Property do the other process you need (as shown on your entity class). No need to check for clicks or any other event. If the user changes the value in the textbox it changes the slider and visa versa.
<Slider Name="slider"
VerticalAlignment="Top"
Value="{Binding Count, Mode=TwoWay}"
Width="200"
Minimum="0"
Maximum="100" />
<TextBox VerticalAlignment="Top"
HorizontalAlignment="Left"
Grid.Column="1"
Width="100"
Text="{Binding Count,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
Height="25" />

Related

WPF Binding to UserControl Custom DependencyProperty

I have a custom UserControl called SongDescription:
<UserControl x:Class="DPTestAp.SongDescription" ...>
<Grid x:Name="LayoutRoot">
<DockPanel Height="50">
<TextBlock x:Name="title" Text="{Binding name}" Width="100" Height="30"/>
<TextBox x:Name="lyrics"/>
</DockPanel>
</Grid>
</UserControl>
I added DependencyProperty to it:
public partial class SongDescription : UserControl
{
public static readonly DependencyProperty SongProperty = DependencyProperty.Register("Song", typeof(Song), typeof(SongDescription));
public Song Song
{
get
{
return (Song)GetValue(SongProperty);
}
set
{
SetValue(SongProperty, value);
updateLyrics()
}
}
private void updateLyrics()
{
lyrics.Text = Song.lyrics;
}
public SongDescription()
{
InitializeComponent();
}
}
The question is: how to bind something to this SongProperty?
I use SongDescription in my main window like this:
<local:SongDescription x:Name="songDescription" Song="{Binding DataContext}"/>
I cannot make my TextBox lyrics show lyrics. In main window I tried to set DataContext to songDescription, like this:
songDescription.DataContext = new Song() { name="Home", lyrics="Hold on, to me as we go" };
or to window itself like this:
DataContext = new Song() { name="Home", lyrics="Hold on, to me as we go" };
I even tried to make Song a resource and bind it to SongProperty like this:
<Window.Resources>
<local:Song x:Key="res" name="Home" lyrics="Hold on, to me as we go"/>
</Window.Resources>
<Grid>
<local:SongDescription x:Name="songDescription" Song="{StaticResource res}"/>
</Grid>
Nothing helped. TextBlock title binds song name fine. But I can't make updateLyrics() method be called. (In real life this method is more complicated, so I can't use Binding like with name).
Thank you!
Yup, so that's a gotcha with dependency properties. You never ever put validation code inside of the accessor methods (get/set) because dependency properties are stored by WPF in a table that it itself manages. This is why you have to register dependency properties, it essentially creates entries on this table for storing the values associated with each property, and when you use 'GetValue' / 'SetValue' you are updating the entries on this table (which by the way relates to how WPF is able to manage data bindings in general).
The upshot of this though is that WPF can (and will) completely bypass your property accessors because it has direct access to the real data. Why should it use your accessors if it can just go to the data directly. Instead you need to implement a 'PropertyChanged' callback function or some WPF sanctioned method of doing validation, but never ever do it in your accessors.
See:
http://msdn.microsoft.com/en-us/library/ms752914.aspx
In addition to sircodesalot's answer, you are not bound on your lyrics textbox. Also, since the song your bound to is a class, you will need to specify the paths fully for the properties you want to show in the boxes such as "Path=Song.Lyrics".
Another thing to consider is that with dependency properties; your mode will be oneway by default so making the text field editable would be moot really unless you change it.
Third, if you're using MVVM you only need your main window context to be set to the view model and have a matching Song property to bind against.

Silverlight MVVM and dealing with FOCUS

I'm developing complex data entry forms with various pop-up lookups, etc. Because of different things - focus of certain controls get lost and I need some way to set focus in MVVM. So far I came up with attached property which I coded like this(actual dependency property declaration skipped):
private static void SetFocus(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBox = d as TextBox;
if (textBox != null)
{
textBox.Focus();
}
}
So, it's pretty simple. When property changes - focus get's set.
My view:
<TextBox Text="{Binding CurrentItem.SerialNumber, Mode=TwoWay, NotifyOnValidationError=True}"
behaviors:TextBoxBehaviors.IsFocused="{Binding SecondaryControlFocus}"
Grid.Column="1" Grid.Row="2" Margin="1" Grid.ColumnSpan="2" TabIndex="2" />
As you see - I attach that behavior and Bind to "SecondaryControlFocus" property.
ViewModel:
public bool SecondaryControlFocus
{
get
{
return this.secondaryControlFocus;
}
set
{
this.secondaryControlFocus = value;
this.RaisePropertyChanged(() => this.SecondaryControlFocus);
}
}
And code how I set focus:
this.SecondaryControlFocus = !this.SecondaryControlFocus;
To me this code smells because I have to toggle property force and back in order to trigger property..
Is there nicer way to accomplish what I do? There is nothing more irritating when power user can't use TAB keys... And I need to get control over focusing in MVVM, this is important for proper data entry flow. But I want code to be somewhat "nice"
It does smell, but I don't think there's anything we can do about it
Usually I do the same thing you have with the AttachedProperty, and keep a single IsFocused bool somewhere in the View (since this is a View-Specific problem, and should not be mixed in with the business logic). I'll then have the View listen to some kind of Event System such as (PRISM's EventAggregator or MVVM Light's Messenger) for ResetFocus events, and I'll raise the ResetFocus event whenever something causes focus to change between my windows/pages, or after a dialog box.
It's not pretty, but it works.

How to pass the selectedItem of a listbox to the View Model

This is a running question that I have updated to hopefully be a little more clear.
In short what I am trying to accomplish is pass a property from a listbox selected item to the viewmodel so that this property can be used within a new query. In the code below the Listbox inherits databinding from the parent object. The listbox contains data templates (user controls) used to render out detailed results.
The issue I am having is that within the user control I have an expander which when clicked calls a command from the ViewModel. From what I can see the Listbox object is loosing it's data context so in order for the command to be called when the expander is expanded I have to explicitly set the datacontext of the expander. Doing this seems to instantiate a new view model which resets my bound property (SelectedItemsID) to null.
Is there a way to pass the selected item from the view to the viewmodel and prevent the value from being reset to null when a button calls a command from within the templated listbox item?
I realize that both Prism and MVVMLite have workarounds for this but I am not familiar with either framework so I don't know the level of complexity in cutting either of these into my project.
Can this be accomplished outside of Prism or MVVMLite?
original post follows:
Within my project I have a listbox usercontrol which contains a custom data template.
<ListBox x:Name="ResultListBox"
HorizontalAlignment="Stretch"
Background="{x:Null}"
BorderThickness="0"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding SearchResults[0].Results,
Mode=TwoWay}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectionChanged="ResultListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<dts:TypeTemplateSelector Content="{Binding}" HorizontalContentAlignment="Stretch">
<!-- CFS Template -->
<dts:TypeTemplateSelector.CFSTemplate>
<DataTemplate>
<qr:srchCFS />
</DataTemplate>
</dts:TypeTemplateSelector.CFSTemplate>
<!-- Person Template -->
<dts:TypeTemplateSelector.PersonTemplate>
<DataTemplate>
<qr:srchPerson />
</DataTemplate>
</dts:TypeTemplateSelector.PersonTemplate>
<!-- removed for brevity -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
SelectionChanged calls the following method from the code behind
private void ResultListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (((ListBox)sender).SelectedItem != null)
_ViewModel.SelectedItemID = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
this.NotifyPropertyChanged(_ViewModel.SelectedItemID);//binds to VM
}
Within the ViewModel I have the following property
public string SelectedItemID
{
get
{
return this._SelectedItemID;
}
set
{
if (this._SelectedItemID == value)
return;
this._SelectedItemID = value;
}
}
the listbox template contains a custom layout with an expander control. The expander control is used to display more details related to the selected item. These details (collection) are created by making a new call to my proxy. To do this with an expander control I used the Expressions InvokeCommandAction
<toolkit:Expander Height="auto"
Margin="0,0,-2,0"
Foreground="#FFFFC21C"
Header="View Details"
IsExpanded="False"
DataContext="{Binding Source={StaticResource SearchViewModelDataSource}}"
Style="{StaticResource DetailExpander}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Expanded">
<i:InvokeCommandAction Command="{Binding GetCfsResultCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Within the ViewModel the delegate command GetCFSResultCommandExecute which is called is fairly straight forward
private void GetCfsResultCommandExecute(object parameter)
{
long IdResult;
if (long.TryParse(SelectedItemID, out IdResult))
{
this.CallForServiceResults = this._DataModel.GetCFSResults(IdResult);}
The issue I am experiencing is when selecting a listbox Item the selectionchanged event fires and the property SelectedItemID is updated with the correct id from the selected item. When I click on the expander the Command is fired but the property SelectedItemID is set to null. I have traced this with Silverlight-Spy and the events are consistent with what you would expect when the expander is clicked the listbox item loses focus, the expander (toggle) gets focus and there is a LeftMouseDownEvent but I cannot see anything happening that explains why the property is being set to null. I added the same code used in the selection changed event to a LostFocus event on the listboxt item and still received the same result.
I'd appreciate any help with understanding why the public property SelectedItemID is being set to null when the expander button which is part of the listbox control is being set to null. And of course I would REALLY appreciate any help in learning how prevent the property from being set to null and retaining the bound ID.
Update
I have attempted to remove the datacontext reference from the Expander as this was suggested to be the issue. From what I have since this is a data template item it "steps" out of the visual tree and looses reference to the datacontext of the control which is inherited from the parent object. If I attempt to set the datacontext in code for the control all bindings to properties are lost.
My next attempt was to set the datacontext for the expander control within the constructor as
private SearchViewModel _ViewModel;
public srchCFS()
{
InitializeComponent();
this.cfsExpander.DataContext = this._ViewModel;
}
This approach does not seem to work as InvokeCommandAction is never fired. This command only seems to trigger if data context is set on the expander.
thanks in advance
With this line you create a new SearchViewModelDataSource using its default constructor.
DataContext="{Binding Source={StaticResource SearchViewModelDataSource}}"
I guess this is why you find null because this is the default value for reference type.
You can resolve the issue by setting DataContext to the same instance used to the main controll (you can do it by code after all components are initialized).
Hope this help!
Edit
I don't think that binding may be lost after setting datacontext from code. I do it every time I need to share something between two or more model.
In relation to the code you've written :
private SearchViewModel _ViewModel;
public srchCFS()
{
InitializeComponent();
this.cfsExpander.DataContext = this._ViewModel;
}
Instead of using this.cfsExpander you can try to use the FindName method. Maybe this will return you the correct instance.
object item = this.FindName("expander_name");
if ((item!=null)&&(item is Expander))
{
Expander exp = item as Expander;
exp.DataContext = this._ViewModel;
}
Try if its work for you.
Of course, this._ViewModel has to expose a property of type ICommand named GetCfsResultCommand but I think this has been already done.
While this was a hacky approach I found an intermediate solution to get the listbox item value to the view model. I ended up using the selection changed event and passing the value directly to a public property wihtin my view model. Not the best approach but it resolved the issue short term
private void ResultListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (((ListBox)sender).SelectedItem != null)
_ViewModel.SelectedItemID = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
MySelectedValue = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
this.NotifyPropertyChanged(_ViewModel.SelectedItemID);
}
For this to fire I did have to also setup a property changed handler within the view to push the change to the VM. You can disregard the MySelectedValue line as it is secondary code I have in place for testing.
For those intereted the generic property changed handler
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}

WPF -- How to bubble up event from Collection class to main Window

I'm using an ObjectDataProvider to call a class of with IObservableCollection:
<ObjectDataProvider x:Key="WaitingPatientDS"
ObjectType="{x:Type local:clsPatients}">
<ObjectDataProvider.ConstructorParameters>
<sys:Boolean>True</sys:Boolean>
</ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>
clsPatient grabs the data and fills the collection. It also uses a timer to poll the requery the database on interval.
Question: how do I create an event for StartPoll and EndPoll in the clsPatient, and more importantly, how do I bubble those events up to the codebehind of my WPF Window?
I'm not clear on what is connecting to whom and how, so let me tell you how I'd do it.
ODP constructs an instance of clsPatients, which contains a "collection" filled with "data".
public class clsPatients, INotifyPropertyChanged
{
public IBindingList Data {get;private set;}
private DispatcherTimer _timer;
public ClsPatients()
{
_timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromMilliseconds(someInterval);
_timer.Tick += DispatcherTimerTick;
_timer.Start();
}
/* etc etc */
}
clsPatients also has a DispatcherTimer which, on a regular interval updates the Data property and fires PropertyChanged
public void DispatcherTimerTick(object sender, EventArgs e)
{
Data = new BindingList(Repository.GetMyDataLol());
// standard event firing method here, move along:
OnPropertyChanged("Data");
}
In the UI, I'd bind against this collection thusly (this may be bug free, or maybe its not):
<ItemsControl
ItemsSource="{Binding Data Source={StaticResource WaitingPatientDS}}">
<ItemsControl.Resources>
<DataTemplate>
<!-- yadda -->
How this works to update the UI when Data is updated:
clsPatient is provided to the ItemsControl by the ObjectDataProvider
ItemsControl uses the WPF binding infrastructure to bind against the Data property of the instance provided by the ODP.
The DispatcherTimer (operating on the UI thread) of the clsPatient updates Data and fires PropertyChanged, which notifies all bindings subscribing to this event that the property has ironically enough changed
The binding takes over and refreshes the ItemsControl
To show an animation that indicates loading is in progress, add another property to clsPatient called Loading:
public Visibility Loading{get;private set}
and update the timer tick event:
public void DispatcherTimerTick(object sender, EventArgs e)
{
Loading = Visibility.Visible;
OnPropertyChanged("Loading");
Data = new BindingList(Repository.GetMyDataLol());
OnPropertyChanged("Data");
Loading = Visibility.Hidden;
OnPropertyChanged("Loading");
}
then, in the ui, bind your indicator's Visibility property to Loading:
<Grid DataContext="{Binding Data Source={StaticResource WaitingPatientDS}}">
<ItemsControl
ItemsSource="{Binding Data}">
<ItemsControl.Resources>
<DataTemplate>
<!-- yadda -->
</ItemsControl>
<Image Source="hurf.jpg" Visibility="{Binding Loading}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
the Image (or whatever other control you want to use) appears when Loading is set to Visible and goes away when its set to Hidden. So you can show the image when you're loading data.
If the UI isn't updating, the process is probably blocking the UI as its executing in the UI thread.
To fix this, run a System.Threading.Timer instead of a DispatcherTimer. STT runs on a background thread other than the UI. In the timer's callback method, use the dispatcher to update the ui (the Invoke methods may be buggy; check the docs on Dispatcher.Invoke):
public void UpdateData(Object stateInfo)
{
var disp = Dispatcher.CurrentDispatcher();
Loading = Visibility.Visible;
disp.Invoke(() => { OnPropertyChanged("Loading");});
// optional sleep here
Data = new BindingList(Repository.GetMyDataLol());
disp.Invoke(() => { OnPropertyChanged("Data");});
Loading = Visibility.Hidden;
disp.Invoke(() => { OnPropertyChanged("Loading");});
}

WPF Databind Before Saving

In my WPF application, I have a number of databound TextBoxes. The UpdateSourceTrigger for these bindings is LostFocus. The object is saved using the File menu. The problem I have is that it is possible to enter a new value into a TextBox, select Save from the File menu, and never persist the new value (the one visible in the TextBox) because accessing the menu does not remove focus from the TextBox. How can I fix this? Is there some way to force all the controls in a page to databind?
#palehorse: Good point. Unfortunately, I need to use LostFocus as my UpdateSourceTrigger in order to support the type of validation I want.
#dmo: I had thought of that. It seems, however, like a really inelegant solution for a relatively simple problem. Also, it requires that there be some control on the page which is is always visible to receive the focus. My application is tabbed, however, so no such control readily presents itself.
#Nidonocu: The fact that using the menu did not move focus from the TextBox confused me as well. That is, however, the behavior I am seeing. The following simple example demonstrates my problem:
<Window x:Class="WpfApplication2.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">
<Window.Resources>
<ObjectDataProvider x:Key="MyItemProvider" />
</Window.Resources>
<DockPanel LastChildFill="True">
<Menu DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="Save" Click="MenuItem_Click" />
</MenuItem>
</Menu>
<StackPanel DataContext="{Binding Source={StaticResource MyItemProvider}}">
<Label Content="Enter some text and then File > Save:" />
<TextBox Text="{Binding ValueA}" />
<TextBox Text="{Binding ValueB}" />
</StackPanel>
</DockPanel>
</Window>
using System;
using System.Text;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication2
{
public partial class Window1 : Window
{
public MyItem Item
{
get { return (FindResource("MyItemProvider") as ObjectDataProvider).ObjectInstance as MyItem; }
set { (FindResource("MyItemProvider") as ObjectDataProvider).ObjectInstance = value; }
}
public Window1()
{
InitializeComponent();
Item = new MyItem();
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(string.Format("At the time of saving, the values in the TextBoxes are:\n'{0}'\nand\n'{1}'", Item.ValueA, Item.ValueB));
}
}
public class MyItem
{
public string ValueA { get; set; }
public string ValueB { get; set; }
}
}
I found that removing the menu items that are scope depended from the FocusScope of the menu causes the textbox to lose focus correctly. I wouldn't think this applies to ALL items in Menu, but certainly for a save or validate action.
<Menu FocusManager.IsFocusScope="False" >
Assuming that there is more than one control in the tab sequence, the following solution appears to be complete and general (just cut-and-paste)...
Control currentControl = System.Windows.Input.Keyboard.FocusedElement as Control;
if (currentControl != null)
{
// Force focus away from the current control to update its binding source.
currentControl.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
currentControl.Focus();
}
This is a UGLY hack but should also work
TextBox focusedTextBox = Keyboard.FocusedElement as TextBox;
if (focusedTextBox != null)
{
focusedTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
This code checks if a TextBox has focus... If 1 is found... update the binding source!
Suppose you have a TextBox in a window, and a ToolBar with a Save button in it. Assume the TextBox’s Text property is bound to a property on a business object, and the binding’s UpdateSourceTrigger property is set to the default value of LostFocus, meaning that the bound value is pushed back to the business object property when the TextBox loses input focus. Also, assume that the ToolBar’s Save button has its Command property set to ApplicationCommands.Save command.
In that situation, if you edit the TextBox and click the Save button with the mouse, there is a problem. When clicking on a Button in a ToolBar, the TextBox does not lose focus. Since the TextBox’s LostFocus event does not fire, the Text property binding does not update the source property of the business object.
Obviously you should not validate and save an object if the most recently edited value in the UI has not yet been pushed into the object. This is the exact problem Karl had worked around, by writing code in his window that manually looked for a TextBox with focus and updated the source of the data binding. His solution worked fine, but it got me thinking about a generic solution that would also be useful outside of this particular scenario. Enter CommandGroup…
Taken from Josh Smith’s CodeProject article about CommandGroup
Simple solution is update the Xaml code as shown below
<StackPanel DataContext="{Binding Source={StaticResource MyItemProvider}}">
<Label Content="Enter some text and then File > Save:" />
<TextBox Text="{Binding ValueA, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding ValueB, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
I've run into this issue and the best solution I've found was to change the focusable value of the button (or any other component such as MenuItem) to true:
<Button Focusable="True" Command="{Binding CustomSaveCommand}"/>
The reason it works, is because it forces the button to get focused before it invokes the command and therefore makes the TextBox or any other UIElement for that matter to loose their focus and raise lost focus event which invokes the binding to be changed.
In case you are using bounded command (as I was pointing to in my example), John Smith's great solution won't fit very well since you can't bind StaticExtension into bounded property (nor DP).
Have you tried setting the UpdateSourceTrigger to PropertyChanged? Alternatively, you could call the UpdateSOurce() method, but that seems like a bit overkill and defeats the purpose of TwoWay databinding.
Could you set the focus somewhere else just before saving?
You can do this by calling focus() on a UI element.
You could focus on whatever element invokes the "save". If your trigger is LostFocus then you have to move the focus somewhere. Save has the advantage that it isn't modified and would make sense to the user.
Since I noticed this issue is still a pain in the ass to solve on a very generic way, I tried various solutions.
Eventually one that worked out for me:
Whenever the need is there that UI changes must be validated and updated to its sources (Check for changes upon closeing a window, performing Save operations, ...), I call a validation function which does various things:
- make sure a focused element (like textbox, combobox, ...) loses its focus which will trigger default updatesource behavior
- validate any controls within the tree of the DependencyObject which is given to the validation function
- set focus back to the original focused element
The function itself returns true if everything is in order (validation is succesful) -> your original action (closeing with optional asking confirmation, saveing, ...) can continue. Otherwise the function will return false and your action cannot continue because there are validation errors on one or more elements (with the help of a generic ErrorTemplate on the elements).
The code (validation functionality is based on the article Detecting WPF Validation Errors):
public static class Validator
{
private static Dictionary<String, List<DependencyProperty>> gdicCachedDependencyProperties = new Dictionary<String, List<DependencyProperty>>();
public static Boolean IsValid(DependencyObject Parent)
{
// Move focus and reset it to update bindings which or otherwise not processed until losefocus
IInputElement lfocusedElement = Keyboard.FocusedElement;
if (lfocusedElement != null && lfocusedElement is UIElement)
{
// Move to previous AND to next InputElement (if your next InputElement is a menu, focus will not be lost -> therefor move in both directions)
(lfocusedElement as UIElement).MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous));
(lfocusedElement as UIElement).MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
Keyboard.ClearFocus();
}
if (Parent as UIElement == null || (Parent as UIElement).Visibility != Visibility.Visible)
return true;
// Validate all the bindings on the parent
Boolean lblnIsValid = true;
foreach (DependencyProperty aDependencyProperty in GetAllDependencyProperties(Parent))
{
if (BindingOperations.IsDataBound(Parent, aDependencyProperty))
{
// Get the binding expression base. This way all kinds of bindings (MultiBinding, PropertyBinding, ...) can be updated
BindingExpressionBase lbindingExpressionBase = BindingOperations.GetBindingExpressionBase(Parent, aDependencyProperty);
if (lbindingExpressionBase != null)
{
lbindingExpressionBase.ValidateWithoutUpdate();
if (lbindingExpressionBase.HasError)
lblnIsValid = false;
}
}
}
if (Parent is Visual || Parent is Visual3D)
{
// Fetch the visual children (in case of templated content, the LogicalTreeHelper will return no childs)
Int32 lintVisualChildCount = VisualTreeHelper.GetChildrenCount(Parent);
for (Int32 lintVisualChildIndex = 0; lintVisualChildIndex < lintVisualChildCount; lintVisualChildIndex++)
if (!IsValid(VisualTreeHelper.GetChild(Parent, lintVisualChildIndex)))
lblnIsValid = false;
}
if (lfocusedElement != null)
lfocusedElement.Focus();
return lblnIsValid;
}
public static List<DependencyProperty> GetAllDependencyProperties(DependencyObject DependencyObject)
{
Type ltype = DependencyObject.GetType();
if (gdicCachedDependencyProperties.ContainsKey(ltype.FullName))
return gdicCachedDependencyProperties[ltype.FullName];
List<DependencyProperty> llstDependencyProperties = new List<DependencyProperty>();
List<FieldInfo> llstFieldInfos = ltype.GetFields(BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static).Where(Field => Field.FieldType == typeof(DependencyProperty)).ToList();
foreach (FieldInfo aFieldInfo in llstFieldInfos)
llstDependencyProperties.Add(aFieldInfo.GetValue(null) as DependencyProperty);
gdicCachedDependencyProperties.Add(ltype.FullName, llstDependencyProperties);
return llstDependencyProperties;
}
}
The easiest way is to set the focus somewhere.
You can set the focus back immediately, but setting the focus anywhere will trigger the LostFocus-Event on any type of control and make it update its stuff:
IInputElement x = System.Windows.Input.Keyboard.FocusedElement;
DummyField.Focus();
x.Focus();
Another way would be to get the focused element, get the binding element from the focused element, and trigger the update manually. An example for TextBox and ComboBox (you would need to add any control type you need to support):
TextBox t = Keyboard.FocusedElement as TextBox;
if ((t != null) && (t.GetBindingExpression(TextBox.TextProperty) != null))
t.GetBindingExpression(TextBox.TextProperty).UpdateSource();
ComboBox c = Keyboard.FocusedElement as ComboBox;
if ((c != null) && (c.GetBindingExpression(ComboBox.TextProperty) != null))
c.GetBindingExpression(ComboBox.TextProperty).UpdateSource();
What do you think about this? I believe I've figured out a way to make it a bit more generic using reflection. I really didn't like the idea of maintaining a list like some of the other examples.
var currentControl = System.Windows.Input.Keyboard.FocusedElement;
if (currentControl != null)
{
Type type = currentControl.GetType();
if (type.GetMethod("MoveFocus") != null && type.GetMethod("Focus") != null)
{
try
{
type.GetMethod("MoveFocus").Invoke(currentControl, new object[] { new TraversalRequest(FocusNavigationDirection.Next) });
type.GetMethod("Focus").Invoke(currentControl, null);
}
catch (Exception ex)
{
throw new Exception("Unable to handle unknown type: " + type.Name, ex);
}
}
}
See any problems with that?
Using BindingGroup will help to understand and mitigate this kind of problem.
Sometimes we consider to apply MVVM model against WPF data bindings.
For example, we consider about mail's subject property:
<TextBox x:Name="SubjectTextBox" Text="{Binding Subject}" />
TextBox SubjectTextBox is on side of View.
The bound property like ViewModel.Subject will belong to ViewModel.
The problem is that changes remain to View in this case.
When we close the WPF window, WPF TextBox won't loose focus on window close.
It means data binding won't perform writing back, and then changes are lost silently.
Introducing of BindingGroup helps to control whether we should apply changes: from View to ViewModel.
BindingGroup.CommitEdit(); will ensure apply changes of direction View → ViewModel
BindingGroup.CancelEdit(); will ensure to discard changes on View.
If you don't call neither, changes are lost silently!
In the following sample, we attach RibbonWindow_Closing event handler so that we can deal with this case of problem.
XAML:
<R:RibbonWindow Closing="RibbonWindow_Closing" ...>
<FrameworkElement.BindingGroup>
<BindingGroup />
</FrameworkElement.BindingGroup>
...
</R:RibbonWindow>
C#
private void RibbonWindow_Closing(object sender, CancelEventArgs e) {
e.Cancel = !NeedSave();
}
bool NeedSave() {
if (!BindingGroup.CommitEdit()) {
// There may be validation error.
return false; // changes this to true to allow closing.
}
// Insert your business code to check modifications.
// return true; if Saved/DontSave/NotChanged
// return false; if Cancel
}
It should work.

Resources