I know what you think: It is 2017, please don't come up with this again, but I really can not find any valueable explanation for this.
Please have a look at the ActiveNotes property in this XAML-Code.
I have this TwoWay binding in my XAML, which works perfectly. It is ALWAYS updated, if the PropertyChanged event for ScaleNotes is fired and if the binding is set to TwoWay.
<c:Keyboard
Grid.Row="2"
Grid.Column="0"
PlayCommand="{Binding PlayCommand}"
StopCommand="{Binding StopCommand}"
ActiveNotes="{Binding ScaleNotes, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The ScaleNotes property in the ViewModel looks like this. Whenever it changes, the PropertyChanged event is guaranteed to be fired. I checked and double checked it. The business logic in the ViewModel works.
private ReadOnlyCollection<eNote> _ScaleNotes;
public ReadOnlyCollection<eNote> ScaleNotes
{
get { return _ScaleNotes; }
set { SetField(ref _ScaleNotes, value); }
}
[DebuggerStepThrough]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
[DebuggerStepThrough]
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
Up to here everything is ok. Whenever the ScaleNotes property in the VM is changed, the target property ActiveNotes is updated.
Now the problem:
If I only change the binding in the XAML to OneWay and the business logic in the VM stays 100% the same, the ActivesNotes property in the target object is updated only once even if the PropertyChanged event is fired. I checked and double checked it. The PropertyChanged event for the ScaleNotes property is always fired.
<c:Keyboard
Grid.Row="2"
Grid.Column="0"
PlayCommand="{Binding PlayCommand}"
StopCommand="{Binding StopCommand}"
ActiveNotes="{Binding ScaleNotes, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
Just to make this complete, here is DP in the target object.
public static DependencyProperty ActiveNotesProperty = DependencyProperty.Register(
"ActiveNotes",
typeof(ReadOnlyCollection<eNote>),
typeof(Keyboard),
new PropertyMetadata(OnActiveNotesChanged));
public ReadOnlyCollection<eNote> ActiveNotes
{
get
{
return (ReadOnlyCollection<eNote>)GetValue(ActiveNotesProperty);
}
set
{
SetValue(ActiveNotesProperty, value);
}
}
private static void OnActiveNotesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Keyboard keyboard = (Keyboard)d;
keyboard.ActiveNotes = (ReadOnlyCollection<eNote>)e.NewValue;
if ((keyboard.ActiveNotes != null) && (keyboard.ActiveNotes.Count > 0))
{
keyboard.AllKeys.ForEach(k => { if ( k.Note != eNote.Undefined) k.IsActiveKey = true; });
keyboard.AllKeys.ForEach(k => { if ((k.Note != eNote.Undefined) && (!keyboard.ActiveNotes.Contains(k.Note))) k.IsActiveKey = false; });
}
else
{
keyboard.AllKeys.ForEach(k => { if (k.Note != eNote.Undefined) k.IsActiveKey = true; });
}
}
I don't understand this. From my knowledge, OneWay and TwoWay only define in which direction the values are updated and not how often they can be updated.
I can not understand, that everything works fine with TwoWay, the business logic stays 100% the same and OneWay is a deal breaker.
If you ask yourself, why I want to know this: This binding was planned as a OneWay binding. It makes no sense to update the source in any way. I only changed it to TwoWay, because OneWay doesn't work as expected.
SOLUTION with the help of #MikeStrobel: (see comments)
The code needs be changed this way:
private static void OnActiveNotesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Keyboard keyboard = (Keyboard)d;
//THIS LINE BREAKED THE CODE, WHEN USING OneWay binding BUT NOT WITH TwoWay binding
//keyboard.ActiveNotes = (ReadOnlyCollection<eNote>)e.NewValue;
if ((keyboard.ActiveNotes != null) && (keyboard.ActiveNotes.Count > 0))
{
keyboard.AllKeys.ForEach(k => { if ( k.Note != eNote.Undefined) k.IsActiveKey = true; });
keyboard.AllKeys.ForEach(k => { if ((k.Note != eNote.Undefined) && (!keyboard.ActiveNotes.Contains(k.Note))) k.IsActiveKey = false; });
}
else
{
keyboard.AllKeys.ForEach(k => { if (k.Note != eNote.Undefined) k.IsActiveKey = true; });
}
}
Using a OneWay binding, the assignment in the OnActiveNotesChanged event handler method deletes or clears out the binding. Mike is correct saying, that the assingment is completely unnecessary, because at this point of time the value is already set before in the property. So it makes no sense at all, no matter if I use OneWay or TwoWay binding.
Dependency properties have a complex system of precedence. The value of a dependency property at any given time may come from various sources: bindings, style setters, trigger setters, etc. Local values have the highest priority, and when you set a local value, you suppress values coming from other sources.
In the case of a binding, setting a local value will cause a source-to-target binding (OneWay or OneTime) to be *removed*. However, when you set a local value on a property with a target-to-source binding (TwoWay or OneWayToSource), the binding will be maintained, and the local value you assigned will get propagated back to the source.
In your case, the issue is here:
keyboard.ActiveNotes = (ReadOnlyCollection<eNote>)e.NewValue;
In your OnActiveNotesChanged handler, you're assigning a new local value to ActiveNotes, which is causing your OneWay binding to be removed. Fortunately, the solution is simple: you can remove this line entirely, as it is redundant. In most cases it is unnecessary to assign a dependency property in its own change handler—the new value has already been applied. (And if you want an opportunity to replace a proposed value before it gets applied, the place to do that would be a CoerceValueCallback, which you can also specify in you PropertyMetadata.)
Let me share my experience after spending sleepless nights on this issue!
I stumbled into this issue on a slightly different use case for which I could not find a solution, but thanks to the advice above I could finally solve it.
To put it short, I faced the same Binding dissociation issue but without having code behind accessing the incriminated property at all! The issue occurred on a CustomControl!
In fact, you must also be aware that if a CustomControl declares a DependencyProperty which is used in a Binding both by the consumer of the CustomControl and inside the Template of the CustomControl, then you MUST ensure that our Binding in the Template of the CustomControl is eighter of type TemplateBinding or a regular binding using OneWay or OneWayToSource.
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register("IsChecked", typeof(bool), typeof(QAToggle), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.None));
public bool IsChecked
{
get => (bool)this.GetValue(IsCheckedProperty);
set => this.SetValue(IsCheckedProperty, value);
}
If not, the Binding used in the UserControl's template when updating will also set a new value to the CustomControls Property thus removing the external binding set up by the consumer of the CustomControl.
<Style TargetType="{x:Type ccont:QAToggle}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="MinHeight" Value="23" />
<Setter Property="MinWidth" Value="75" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ccont:QAToggle}">
<ToggleButton x:Name = "QAToggle"
Command = "{TemplateBinding Command}"
CommandParameter = "{TemplateBinding CommandParameter}"
FontSize = "{TemplateBinding FontSize}"
FontWeight = "{TemplateBinding FontWeight}"
FontFamily = "{TemplateBinding FontFamily}"
IsThreeState = "False"
IsChecked = "{Binding IsChecked, RelativeSource={RelativeSource AncestorType=ccont:QAToggle}}">
Here the line IsChecked = "{Binding IsChecked, RelativeSource={RelativeSource AncestorType=ccont:QAToggle}}" will break the external binding. To avoid this change it to:
IsChecked = "{TemplateBinding IsChecked}"
or
IsChecked = "{Binding IsChecked, RelativeSource={RelativeSource AncestorType=ccont:QAToggle}, Mode=OneWay}"
Related
This is for a Windows 10 Universal App.
XAML:
<RelativePanel Padding="4" Margin="4,12,0,0">
<TextBlock x:Name="Label" Text="Class Name" Margin="12,0,0,4"/>
<ListView x:Name="ClassTextBoxes"
ItemsSource="{Binding TextBoxList}"
SelectionMode="None" RelativePanel.Below="Label">
<ListView.ItemTemplate>
<DataTemplate >
<RelativePanel>
<TextBox x:Name="tbox"
PlaceholderText="{Binding PlaceHolder}"
Text="{Binding BoxText,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Padding="4" Width="200" MaxLength="25"/>
<TextBlock x:Name="errorLabel"
RelativePanel.Below="tbox"
Text="{Binding Error, Mode=TwoWay}"
Padding="0,0,0,4"
FontSize="10"
Foreground="Red"/>
<Button Content="Delete" Margin="12,0,0,0" RelativePanel.RightOf="tbox"/>
</RelativePanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</RelativePanel>
Model:
public class TextBoxStrings : BaseModel
{
private string _placeholder;
public string PlaceHolder
{
get { return _placeholder; }
set
{
if (_placeholder != value)
{
_placeholder = value;
NotifyPropertyChanged();
}
}
}
private string _boxText;
public string BoxText
{
get { return _boxText; }
set
{
if (_boxText != value)
{
_boxText = CheckBoxText(value);
NotifyPropertyChanged();
}
}
}
public string CheckBoxText(string val)
{
var r = new Regex("[^a-zA-Z0-9]+");
return r.Replace(val, "");
}
}
ViewModel:
private TrulyObservableCollection<TextBoxStrings> _textBoxList;
public TrulyObservableCollection<TextBoxStrings> TextBoxList
{
get { return _textBoxList; }
set
{
if (_textBoxList != value)
{
_textBoxList = value;
RaisePropertyChanged();
}
}
}
and I add new TextBoxString objects to my TextBoxList collection from within my view-model.
I want to make it that users can't type in certain characters (or rather, they get deleted whenever they
are typed in.
This works...in the model. Setting breakpoints and looking at the values, everything in the Model is working: value goes into the setter and gets changed, _boxText holds the new value that is set from CheckBoxText();
But the problem is, in my View, the textbox doesn't reflect changes to the underlying text that I make in the model.
So if I type in "abc*()" into "tbox", the value in the model will be "abc". The value of the textbox, however, will still be "abc*()".
I have a feeling it has something to do with the fact that I'm editing items that are inside of a collection and I don't have anything implemented to handle changing items within a collection. I was under the impression that using INotifyPropertyChanged and ObservableCollection<T> would take care of that for me.
Does anyone have any suggestions?
Thank you!
Edit: So, now I'm trying to use TrulyObservableCollection because I thought this was the problem, but it hasn't helped. Here it is: https://gist.github.com/itajaja/7507120
But the problem is, in my View, the textbox doesn't reflect changes to the underlying text that I make in the model.
As you've seen, the TextBox do reflect changes to your model. When you type in "abc*()" in the TextBox, the value in the model will be changed to "abc". The problem here is that the binding system in UWP is "intelligent". For TwoWay bindings, changes to the target will automatically propagate to the source and in this scenario, binding system assumes that the PropertyChanged event will fire for corresponding property in source and it ignores these events. So even you have RaisePropertyChanged or NotifyPropertyChanged in you source, the TextBox still won't update.
In WPF, we can call BindingExpression.UpdateTarget Method to force the update. But this method is not available in UWP.
As a workaround, you should be able to use TextBox.TextChanged event to check the input like following:
private void tbox_TextChanged(object sender, TextChangedEventArgs e)
{
var tb = sender as TextBox;
if (tb != null)
{
var originalText = tb.Text;
var r = new Regex("[^a-zA-Z0-9]+");
if (originalText != r.Replace(originalText, ""))
{
var index = (tb.SelectionStart - 1) < 0 ? 0 : (tb.SelectionStart - 1);
tb.Text = r.Replace(originalText, "");
tb.SelectionStart = index;
}
}
}
However it may break your MVVM model, you can use data validation to avoid this and here is a blog: Let’s Code! Handling validation in your Windows Store app (WinRT-XAML) you can refer to. And for my personal opinion, data validation is a better direction for this scenario.
if (_boxText != value)
{
_boxText = CheckBoxText(value);
NotifyPropertyChanged();
}
Try changing this to:
var tmp = CheckBoxText(value);
if (_boxText != tmp)
{
_boxText = tmp;
NotifyPropertyChanged();
}
I hope, in your XAML, the binding to property BoxText is two-way, right?
You should edit BoxText and then send checked value to UI. Just send value to CheckBoxText and already edited should be assigned to _boxText. And then you should send BoxText to UI by calling RaisePropertyChanged("BoxTest"). Please, see the following code snippet:
private string _boxText;
public string BoxText
{
get { return _boxText; }
set
{
if (_boxText != value)
{
_boxText=CheckBoxText(value);
RaisePropertyChanged("BoxText");
}
}
}
There is no difference where you use INotifyPropertyChanged for one property of for properties placed in collection. The complete example with collections and ListView can be seen here
I have simple UserControl where is defined property ItemsSource
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(Dictionary<string, object>), typeof(UserControl1), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(UserControl1.OnItemsSourceChanged)));
public Dictionary<string, object> ItemsSource
{
get { return (Dictionary<string, object>)GetValue(ItemsSourceProperty); }
set
{
SetValue(ItemsSourceProperty, value);
}
}
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UserControl1 control = (UserControl1)d;
control.DisplayInControl();
}
I want to make this property update dynamically, but I am wondered why OnItemsSourceChanged doesn't fired every time when something happend with ItemsSource. So I am upset.
I've tried Custom ItemsSource property for a UserControl but this doesn't help or I've written bad newValueINotifyCollectionChanged_CollectionChanged function
My control is from this post CodeProject
My Code :
UserControl XAML - http://pastie.org/10606317
UserControl CodeBehind - http://pastie.org/10606322
Control Usage -
<controls:MultiSelectComboBox SelectedItems="{Binding SelectedCategories, Mode=TwoWay}" Grid.Column="0" Grid.Row="0" x:Name="CategoriesFilter" DefaultText="Category" ItemsSource="{Binding Categories }" Style="{StaticResource FiltersDropDowns}"/>
Update : I've made small step to solution. I have next style :
<Style.Triggers>
<DataTrigger Binding="{Binding ItemsSource, RelativeSource={RelativeSource Self}}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
<DataTrigger Binding="{Binding ItemsSource.Count, RelativeSource={RelativeSource Self}}" Value="0">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
which I apply to my control (make control disabled if no itemSource). As I update control source on click, I see that control becomes enabled, so ItemsSource aren't empty (from start it is). So problem now is just in Redrawing control content if I understand this behaviour correctly.
If you have a dictionary as your data type, and you add or remove a value from the dictionary, then your property did not actually change. This event will only fire if you have actually set this property to reference a different dictionary.
If you need wpf to automatically detect if an item is added or removed from the dictionary, you should use an ObservableCollection.
Replace Dictionary with ObservableCollection, Dictionary won't fire the propertyChanged event when add, update, delete an item.
Write your own Dictionary to fire the propertychanged event manually.
I see that the problem is that you are building a new ItemsSource for the ComboBox control within your custom UserControl, and that you are expecting that MultiSelectCombo.ItemsSource to stay synced with your UserControl1.ItemsSource. This can't happen when simply binding a Dictionary, and it won't happen even when binding an ObservableCollection -- unless you explicitly handle it.
To accomplish what you are after, first you will need ItemsSource of your custom control to be of a type that does notify us of collection changes, such as the ObservableCollection (which I'll use for this example, as you've done in your links above). Then, you'll need to update the ItemsSource of your ComboBox within your control, to keep them in sync.
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (MultiSelectComboBox)d;
// Since we have to listen for changes to keep items synced, first check if there is an active listener to clean up.
if (e.OldValue != null)
{
((ObservableCollection<Node>)e.OldValue).CollectionChanged -= OnItemsSource_CollectionChanged;
}
// Now initialize with our new source.
if (e.NewValue != null)
{
((ObservableCollection<Node>)e.NewValue).CollectionChanged += OnItemsSource_CollectionChanged;
control.DisplayInControl();
}
}
private void OnItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
DisplayInControl();
}
Now, that said, the solution above is for a more generic case, where you might need to do something with the ItemsSource given to your custom control before passing it onto your MultiSelectCombo.ItemsSource. In your current case, however, you are simply building a collection of Node to exactly match the given collection of Node. If that's guaranteed to be the case, your solution can be much, much simpler:
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (MultiSelectComboBox)d;
control.MultiSelectCombo.ItemsSource = (IEnumerable)e.NewValue;
}
Just let the wrapped ComboBox use the ItemsSource given to your custom control.
I have a DataGrid. One of the columns is a template with a CheckBox in it. When the Checked or Unchecked events trigger, (it happens with both) the CheckBox's DataContext is sometimes null, which causes my code to error. It seems to be null most often if the mouse is moving while you press and release the button quickly (it's intermittent).
I listened for changes to the DataContext of the CheckBox by making views:ListenCheckBox (extends CheckBox) and attaching a binding, and it's never set to null, but it is set from null to a Task at times I wouldn't expect, i.e. after the DataGrid has been totally generated and you're checking/unchecking boxes. Immediately after the [un]checked event runs with a null DataContext, I get the notification that shows the DataContext changed from null to a Task, so it appears that when I get a null DataContext, it's because it hadn't actually set the DataContext by the time it ran the Checked/Unchecked event.
Also, I added Tag="{Binding}" to the CheckBox for debugging. The Tag is not null (i.e. it has the proper object) more often than the DataContext, but still not all the time.
Here are the relevant bits of the XAML code:
<navigation:Page.Resources>
<sdk:DataGridTemplateColumn x:Key="DeleteOrPrintSelect" Header="Delete Or Print Notes Selection">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<views:ListenCheckBox IsChecked="{Binding DeleteOrPrintNotesSelection, Mode=TwoWay}" Checked="DeletePrintNotesCheckBox_Changed" Unchecked="DeletePrintNotesCheckBox_Changed" HorizontalAlignment="Center" VerticalAlignment="Center" Tag="{Binding}" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</navigation:Page.Resources>
<sdk:DataGrid x:Name="dataGrid1" Grid.Column="1" Grid.Row="2" AutoGeneratingColumn="dataGrid1_AutoGeneratingColumn">
<sdk:DataGrid.RowGroupHeaderStyles>
[removed]
</sdk:DataGrid.RowGroupHeaderStyles>
</sdk:DataGrid>
And the relevant code behind:
// Create a collection to store task data.
ObservableCollection<Task> taskList = new ObservableCollection<Task>();
[code adding Tasks to taskList removed]
PagedCollectionView panelListView = new PagedCollectionView(taskList);
this.dataGrid1.ItemsSource = panelListView;
}
private void dataGrid1_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyName == "DeleteOrPrintNotesSelection")
{
e.Column = Resources["DeleteOrPrintSelect"] as DataGridTemplateColumn;
}
else
{
e.Column.IsReadOnly = true;
}
}
private void DeletePrintNotesCheckBox_Changed(object sender, RoutedEventArgs e)
{
try
{
var cb = sender as CheckBox;
var t = cb.DataContext as Task;
t.DeleteOrPrintNotesSelection = cb.IsChecked == true;
PagedCollectionView pcv = this.dataGrid1.ItemsSource as PagedCollectionView;
ObservableCollection<Task> taskList = pcv.SourceCollection as ObservableCollection<Task>;
bool anySelected = taskList.Any(x => x.DeleteOrPrintNotesSelection);
this.btnPrint.IsEnabled = anySelected;
this.btnDelete.IsEnabled = anySelected;
}
catch (Exception ex)
{
ErrorMessageBox.Show("recheck", ex, this);
}
}
Any ideas? Thanks in advance.
I found that the problem happened when you double click on the cell and it moved it to the cell editing template. In my case, I didn't have a cell editing template defined, so it used the same cell template, but instead of not changing anything, it apparently decided to make a new check box. I set the column's IsReadOnly property to true, and it fixed it. An alternate solution:
DataContext="{Binding}" (in XAML, or the code equivalent:)
cb.SetBinding(FrameworkElement.DataContextProperty, new Binding());
I'm not sure why this one fixes it, since I thought the default DataContext is {Binding}. Perhaps it's a Silverlight bug, and it gets set in a different order if you define it explicitly instead of leaving it the default.
I have a itemscontrol with the item template set to a border, then i bind the datacontext of the listview to a list of objects that contain a bool property.
Then i added a click event handler to the border, and when detecting a click, i cast the datacontext of the border to the class and set the bool field to true.
That works as a charm, but i want the rectangle to have a specific colour when the bool field is set to true or false, so i created a IValueConverter that takes my class and returns a colour.
That works too, the rectangles are different colors based on the bool field.
I am still able to click the rectangles, but they just arent updated.
The color of the rectangle wont change.
Datatemplate from the itemscontrol itemtemplate
<DataTemplate>
<Border ToolTip="{Binding Seat.Column}" Width="25" Height="25" Margin="0,0,2,2" BorderBrush="Black" Background="{Binding Converter={StaticResource ResourceKey=SeatStateConverter}}" BorderThickness="2" Name="rectangle1" VerticalAlignment="Center" HorizontalAlignment="Center" MouseLeftButtonDown="rectangle1_MouseLeftButtonDown">
<Label Content="{Binding Occupied}" Foreground="White" FontSize="7"></Label>
</Border>
</DataTemplate>
The click event
private void rectangle1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Border item = sender as Border;
SeatState state = item.DataContext as SeatState;
state.Locked = !state.Locked;
}
my converter
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
SeatState state = value as SeatState;
if (state == null)
return null;
SolidColorBrush brush = new SolidColorBrush();
if (state.Occupied)
{
brush.Color = Color.FromRgb(172, 0,0);
}
else if (state.Locked)
{
brush.Color = Color.FromRgb(214, 65, 0);
}
else if(!state.Occupied)
{
brush.Color = Color.FromRgb(0, 172, 0);
}
return brush;
}
This works great.. untill i add the converter that converts the objects into a SolidColorBrush.
I tried all sorts of crazy stuff that should have nothing to do with my problem.
implementing the convertback method
in the IValueConverter interface
setting the binding to a two way binding
invoking the UpdateLayout
method on the ItemsControl
But nothing seemed to work.
Anyone got any ideas?
My english could be better so please ask if there is anything you want clearified =)
Thanks in advance.
I think you are binding to the SeatState object - whereas you actually need to bind to some combination of the Occupied an Locked properties?
i.e. it's not the SeatState object itself that is changing, but rather its a couple of the properties of the SeatState.
Maybe merge the properties together somehow and set this merged property as the Path for the XAML Background.
e.g. within SeatState
private bool _Locked
public bool Locked
{
get
{
return _Locked;
}
set
{
_Locked = value;
NotifyPropertyChange("Locked");
NotifyPropertyChange("LockedAndOccupied");
}
}
private bool _Occupied
public bool Occupied
{
get
{
return _Occupied;
}
set
{
_Occupied = value;
NotifyPropertyChange("Occupied");
NotifyPropertyChange("LockedAndOccupied");
}
}
public Tuple<bool, bool> LockedAndOccupied
{
get
{
return new Tuple<bool, bool>(Locked, Occupied);
}
}
then in the XAML you can bind to Path=LockedAndOccupied, Converter=...
Obviously you'll have to change the Converter code too - I'll let you do that!
Alternatively... now I've read up about it...
There is something called a MultiBinding - http://www.switchonthecode.com/tutorials/wpf-tutorial-using-multibindings - looks perfect for your needs
Something like:
<Border.Background>
<MultiBinding Converter="{StaticResource aNewConverter}">
<Binding Path="Locked"/>
<Binding Path="Occupied"/>
</MultiBinding>
</Border.Background>
I've learnt something new tonight :)
Check the Background binding... it looks like your Path is missing. I would expect to see something like...
Background="{Binding Path=., Converter={StaticResource ResourceKey=SeatStateConverter}}"
Alternately you could try setting BindsDirectlyToSource=true.
On second thought, you probably need to implement an IMultiValueConverter, and then bind each of the properties separately. That may be what you need to do to get the change notifications on each of the properties. Here is an example of an IMultiValueConverter implementation from MSDN.
Also, you may want to check your implementation of INotifyPropertyChanged... misspellings of property names will break the change notifications...
I have a series of controls that are databound to values that change every second or so. From time to time, I need to "pause" the controls, so that they do not update their databindings (in either direction). I then later need to "unpause" the controls, so that they can update the datasource with their values, and receive future updates from the source as normal. How do I accomplish this?
Sample Binding:
<TextBox Text="{Binding UpdateSourceTrigger=LostFocus, Mode=TwoWay, Path=myData}">
You don't necessarily have to suspend binding. Another, and possibly simpler, way to do this is to suspend change notification in the view model. For instance:
private HashSet<string> _ChangedProperties = new HashSet<string>();
private void OnPropertyChanged(string propertyName)
{
if (_Suspended)
{
_ChangedProperties.Add(propertyName);
}
else
{
PropertyChangedEventHandler h = PropertyChanged;
if (h != null)
{
h(this, new PropertyChangedEventArgs(propertyName));
}
}
}
private bool _Suspended;
public bool Suspended
{
get { return _Suspended; }
set
{
if (_Suspended == value)
{
return;
}
_Suspended = value;
if (!_Suspended)
{
foreach (string propertyName in _ChangedProperties)
{
OnPropertyChanged(propertyName);
}
_ChangedProperties.Clear();
}
}
}
This will (if it's debugged and tested, which I haven't done) stop raising PropertyChanged events when Suspended is set to true, and when Suspended is set to false again it will raise the event for every property that changed while it was suspended.
This won't stop changes to bound controls from updating the view model. I submit to you that if you're letting the user edit properties on the screen at the same time that you're changing them in the background, there's something you need to take a closer look at, and it's not binding.
To deal with the source set the UpdateSourceTrigger to be Explicit.
<TextBox Name="myTextBox" Text="{Binding UpdateSourceTrigger=Explicit, Mode=TwoWay, Path=myData}">
Then in code behind reference a service which can deal with the actual updating as defined by your conditions.
BindingExpression be = myTextBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
This will allow you to specify at which point the data goes back to the source from the target.
The target can be addressed by making a call to the same referenced service which has the knowledge on when to call the INotifyPropertyChanged.PropertyChanged event within your ViewModel.
class Data : INotifyPropertyChanged
{
Manager _manager;
public Data(Manager manager)
{
_manager = manager;
}
public event PropertyChangedEventHandler PropertyChanged;
String _info = "Top Secret";
public String Information
{
get { return _info; }
set
{
_info = value;
if (!_manager.Paused)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("Information"));
}
}
}
}
First of all you need create explicit binding:
Binding binding = new Binding("Content");
binding.Source = source;
binding.UpdateSourceTrigger = UpdateSourceTrigger.LostFocus;
binding.Mode = BindingMode.TwoWay;
txtContent.SetBinding(TextBox.TextProperty, binding);
Then when you need pause twoway binding you need destroy old binding and create new oneway binding with explicit trigger(in this case you binding source will not be updated when some property has been changed):
BindingOperations.ClearBinding(txtContent, TextBlock.TextProperty);
Binding binding = new Binding("Content");
binding.Source = source;
binding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
binding.Mode = BindingMode.OneWay;
txtContent.SetBinding(TextBox.TextProperty, binding);
When you need to resume twoway binding you can explicit update source(if you need it) than destroy oneway binding and create twoway binding.
BindingExpression be = txtContent.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
BindingOperations.ClearBinding(txtContent, TextBlock.TextProperty);
Binding binding = new Binding("Content");
binding.Source = source;
binding.UpdateSourceTrigger = UpdateSourceTrigger.LostFocus;
binding.Mode = BindingMode.TwoWay;
txtContent.SetBinding(TextBox.TextProperty, binding);
If the control you want to suspend has a DataContext (ViewModel) you own, just save it off and null out the DataContext.
If the control has an inherited DataContext, setting that control's DataContext to null will block the inheritance. Then to resume binding updates, you use the ClearValue method to clear the DataContext DependencyProperty so inheritance kicks in again.
You can get fancy and use a VisualBrush to take a screen shot of the control you are suspending before clearing its DataContext, so the user doesn't see the control go blank.
My solution ended up as follows to prevent the text updating while the user is trying to change it.
XAML:
<TextBox Grid.Row="0" Grid.Column="1" TextAlignment="Right" VerticalAlignment="Center" Text="{Binding Path=MinimumValueInDisplayUnit, StringFormat=0.########}" MinWidth="100" Margin="4" GotFocus="TextBox_OnGotFocus" LostFocus="TextBox_OnLostFocus"/>
<TextBox Grid.Row="0" Grid.Column="2" TextAlignment="Right" VerticalAlignment="Center" Text="{Binding Path=MaximumValueInDisplayUnit, StringFormat=0.########}" MinWidth="100" Margin="4" GotFocus="TextBox_OnGotFocus" LostFocus="TextBox_OnLostFocus"/>
Code behind:
private void TextBox_OnGotFocus([CanBeNull] object sender, [CanBeNull] RoutedEventArgs e)
{
TextBox tb = sender as TextBox;
if (tb == null) return;
BindingExpression expression = tb.GetBindingExpression(TextBox.TextProperty);
if (expression == null) return;
// disable updates from source
BindingOperations.ClearBinding(tb, TextBlock.TextProperty);
tb.SetBinding(TextBox.TextProperty, new Binding(expression.ParentBinding.Path.Path) { Mode = BindingMode.OneWayToSource, UpdateSourceTrigger = UpdateSourceTrigger.Explicit , FallbackValue = tb.Text});
}
private void TextBox_OnLostFocus([CanBeNull] object sender, [CanBeNull] RoutedEventArgs e)
{
TextBox tb = sender as TextBox;
if (tb == null) return;
BindingExpression expression = tb.GetBindingExpression(TextBox.TextProperty);
if (expression == null) return;
// send current value to source
expression.UpdateSource();
// enable updates from source
BindingOperations.ClearBinding(tb, TextBlock.TextProperty);
tb.SetBinding(TextBox.TextProperty, new Binding(expression.ParentBinding.Path.Path) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.LostFocus });
}
Note that I assign the current Text as the fallback value of the OneWayToSource binding to have a start value (otherwise the text field would be empty once focused)
If you keep a reference to the view in the controller class you could fire an event from the view model when you want to suspend databinsing that has the controller clear the DataContext of the view. and when you are ready to start sending an receiving data again, reset the Views DataContext to the View Model.