WPF Control binding does not always update UI - c#

I have a button whose IsEnabled property is bound within my view model to a value indicating whether or not the data in the current view has been modified. This binding has been working fine so far until I'd tried adding Drag/Drop functionality to an ItemsControl within the view.
The Drag/Drop function is working fine and does everything it's supposed to. When it's completed any data manipulation it needs to, it sets the View Model's IsModified property to true. I've verified that the value is actually set to true.
The problem I'm having is that, when the IsModified property is changed from within my Drop method, the button's IsEnabled property isn't updating; when IsModified is set to true during the drag/drop operation, the button remains disabled. If I click the button, it suddenly updates and becomes enabled, requiring me to press the button a second time to actually do as it's intended.
The Drop method is being called on the Drop event on the ItemsControl item. Is this being called from another thread or something that is not informing the UI of the property change? I've tried finding supporting docs, but am having a bit of trouble.
Once again, setting the IsModified property continues to work under any other circumstances and updates the UI properly.
The code in question is fairly simple.
XAML:
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<EventSetter Event="DragEnter" Handler="IcFields_DragEnter"/>
<EventSetter Event="Drop" Handler="IcFields_Drop"/>
</Style>
</ItemsControl.ItemContainerStyle>
...
...
...
<Button Margin="5" Padding="10,0,10,0" IsEnabled="{Binding IsModified}"
Command="{Binding SaveChangesCommand}">Save Changes</Button>
where the IcFields_Drop handler allows the changes to be made to the model and, in the end, sets IsModified to true.
Edit:
Here's an abbreviated example of the implementation. Use the XAML above as the XAML for this example.
C# - Code-Behind
private void IcFields_Drop(object sender, DragEventArgs e)
{
ViewModel.Drop();
}
C# - View Model
private bool isModified;
public bool IsModified
{
get { return isModified; }
set { SetProperty(ref isModified, value); }
}
public void Drop()
{
//PSEUDO: Do some drag/drop logic for the items attached to the ItemsControl.
...
...
IsModified = true;
}

Instead of binding the IsEnabled property, you should return a bool from the CanExecute method of your command to indicate whether the Button should be enabled. You would then call a method that raises the CanExecuteChanged event of the command in your Drop() method.
Most ICommand implementations include a RaiseCanExecuteChanged() method or similar that you can call to refresh the status of the command:
public void Drop()
{
...
SaveChangesCommand.RaiseCanExecuteChanged();
}

Related

WPF Dependency property changed but not effectuated in binding

I ran into an issue which I posted about before here. I am still struggling with this issue, so I attempted to break it down in a smaller code setup.
The problem:
I have a binding of dependency property to view model, which does not update the viewmodel with it's changed value #construction time.
The binding seems correct, because changing the value in XAML after the application started (relying on xaml hot reload) does update the view model with changes.
I can reproduce the problem with the following setup:
MainWindow:
<Grid>
<local:UserControl1
SomeText="My changed text"
DataContext="{Binding UserControlViewModel}"/>
</Grid>
MainViewModel:
public class MainViewModel
{
public UserControlViewModel UserControlViewModel { get; set; }
public MainViewModel()
{
UserControlViewModel = new UserControlViewModel();
}
}
UserControl:
<UserControl.Resources>
<Style TargetType="local:UserControl1">
<Setter Property="SomeText" Value="{Binding MyText, Mode=OneWayToSource}"></Setter>
</Style>
</UserControl.Resources>
<Grid>
<TextBlock Text="{Binding MyText}"></TextBlock>
</Grid>
UserControl code behind:
public static readonly DependencyProperty SomeTextProperty = DependencyProperty.Register(
nameof(SomeText),
typeof(string),
typeof(UserControl1),
new PropertyMetadata("default text", PropertyChangedCallback));
public string SomeText { get; set; }
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// first and only update: 'default text' => 'My changed text'
}
public UserControl1()
{
InitializeComponent();
}
UserControl view model:
public class UserControlViewModel
{
// Setter is called with 'default text'
// AFTER the property changed callback is triggered with updated text
public string MyText { get; set; }
}
When I run the app, the text 'default text' is displayed, while I expected 'My changed text'.
When I then change the SomeText property in XAML, then again I see the changed callback fire, and consequently I see the view model setter get updated. This time with the changed value. Thus, the binding seems to work fine, but during startup it fails to update the view model with the (already known) changed value.
Can anybody explain what is causing this issue? Is there a way around this problem?
Update
I just found out, that when I change the XAML (using hot reload) the update sequence is:
first the viewmodel's setter is set
then the OnPropertyChanged callback fires.
the result is that the changed value is displayed on the UI
That's the opposite of what happens at construction time. Then the order is :
The OnPropertyChanged callback fires
The view model's setter is set.
The result is that the default value is displayed on the UI (as described in the original issue)
This is actually really weird. Because when the Property Changed callback fires (during start up) I can cast the DependencyObject back to my UserControl and check its data context. The datacontext is null at the time.
My previous experiment with hot reload proves that eventually the binding works perfect.
Thus step 1 is to set the text 'my changed text' to the dependency property.
Step 2 is to connect the view model as datacontext to MyUserControl
Step 3 is that the bindings are evaulated and in this case, the binding (onewaytosource) gets it's initial sync, but with the old value.
To me, this looks like a bug in WPF.
You are using the wrong binding mode for your use case.
When you specify OneWayToSource, you are allowing the data to flow only from your textbox to the property in your ViewModel, as the source is the MyText property.
Try removing the Mode=OneWayToSource, or use TwoWay if you want the text to be updated both from View and ViewModel. (IIRC TwoWay is the default mode for TextBox control).
Also, is your ViewModel implementing the INotifyPropertyChanged Interface to support the bindings?
A small summary that explains the different modes is in this SO answer

How to debug UWP xaml bindings?

I have a DataGrid that is binded to an ObservableCollection in my universal windows platform app.
The datagrid is not shown up when page is loaded. I have another datagrid in the same page that is almost the same but is binded to another collection almost the same as the first one(which has binding problems).
Is there any way to debug the XAML file ?
Sample Code:
<GridView Name="HourGridView" Grid.Row="4"
ItemsSource="{x:Bind ForeCastsByDates}"
Foreground="Chartreuse" >
<GridView.ItemTemplate>
<DataTemplate x:DataType="data:ForeCast">
.......
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
The collection which is not binded:
private ObservableCollection<ForeCast> ForeCastsByDates;
The collection that binded well:
private ObservableCollection<ForeCast> ForeCasts;
The ForeCastsByDates is a part of ForeCasts:
ForeCastsByDates = new ObservableCollection<ForeCast>( ForeCasts.GroupBy(x => x.Date).Select(x => x.First()));
If I am not wrong, it seems that you are actually trying to bind to a class field not a property.
Data binding requires properties to work properly. To achieve that, you will have to make a private backing field and a public property that can then be accessed with data binding.
private ObservableCollection<ForeCast> _foreCastsByDates;
public ObservableCollection<ForeCast> ForeCastsByDates
{
get
{
return _foreCastsByDates;
}
set
{
_foreCastsByDates = value;
//notify about changes
OnPropertyChanged();
}
}
You may have noticed the property uses a OnPropertyChanged() method in the setter. To actually notify the user interface about changes of the property, you need to implement the INotifyPropertyChanged interface on your Page:
public partial MainPage : Page, INotifyPropertyChanged
{
// your code...
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The OnPropertyChanged method fires the PropertyChanged event which notifies the listeners that a property has changed. In this case we need to notify about the changes to the ForeCastsByDates property. Using the CallerMemberNameAttribute used next to the OnPropertyChanged method parameter, the parameter is automatically set to the name of the caller (in this case the ForeCastsByDates property.
Finally, the {x:Bind} syntax defaults to OneTime mode, which means it is updated only once and does not listen to property changed. To ensure all later updates to the property are reflected, use
{x:Bind ForecastsByDates, Mode=OneWay}
Important thing to mention is that you have to make changes to the ForecastsByDates property itself to notify the UI (the property setter has to be executed to call the OnPropertyChanged method). If you do just _foreCastsByDates = something, the field will change, but the UI will not know about it and the change will not be reflected.

can't bind to IsEnabled property of ToggleButton using caliburn micro

I'm trying to bind the IsEnabled property of a ToggleButton with no success.
once the NotifyOfPropertyChange is fired, I'm getting the following exception:
Value does not fall within the expected range.
Using a simple Button, the above configurations works as expected.
I wonder if there any workaround for that one?
Thanks
UPDATE:
well it took me a while to pinpoint the problem, but finally managed to understand the behavior:
I've created a simple tester where I use a button to enable/disable a ToggleButton.
when the ToggleButton control does not contain anything, all works properly; however, after adding sub controls to it (in our case I just added a StackPanel) an exception is raised:
Value does not fall within the expected range - right after NotifyOfPropertyChange() is called.
Here is the problematic view I'm using:
<StackPanel>
<ToggleButton x:Name="SayHello" Grid.Column="1" IsEnabled="{Binding HasValue}" Height="190">
<StackPanel x:Name="sp"> </StackPanel>
</ToggleButton>
<Button x:Name="Click"></Button>
</StackPanel>
The ViewModel:
private bool _hasvalue;
public bool HasValue
{
get { return _hasvalue; }
set
{
_hasvalue = value;
NotifyOfPropertyChange(() => HasValue);
}
}
public void Click()
{
HasValue = !HasValue;
}
Any way to workaround that one? - the platforms is WP8.
I couldn't replicate the error from the example above, is there additional information in your ViewModel?
you should also be able to get the effect you want (although I'd still be interested to see the root cause of your error), by using the Caliburn.Micro conventions. Is x:Name=sp causing anything to be bound?
If you have a method SayHello, with a UI element bound to the method via a convention: x:Name="SayHello"
You can create a bool property on your ViewModel called CanSayHello, which Caliburn.Micro will use to Enable/Disable the control; although you will have to call NotifyPropertyChanged when that property changes (so the UI is aware and can update the control).
E.g.
<!-- Your existing Control, Note `IsEnabled` is not bound -->
<ToggleButton x:Name="SayHello" Height="40">
// On your ViewModel
public bool CanSayHello
{
get
{
return HasValue;
}
}
public void Click()
{
HasValue = !HasValue;
NotifyOfPropertyChange(() => CanSayHello);
}
Some additional info.

Metro style windows 7 WPF app -toggleSwitch-

I'm currently fiddling around with the look of one of my older wpf apps using MahApps metro library. I'm stuck with Controls:ToggleSwitch where I can bind almost everything but commands.
When I try to bind a command as below,
<Controls:ToggleSwitch Header="Start Playing" OnLabel="Stop" OffLabel="Play"
IsChecked="{Binding ToggleRecordCommand}"
CommandParameter="{Binding}" />
I get an error like;
Error 62 A TwoWay or OneWayToSource binding cannot work on the read-only property 'ToggleRecordCommand' of type 'RecorderApp.View.MainWindowViewModel'.
Also it tells me there is no CommandParameter. How am I going to bind actions to this?
First of all, as Brendan said, IsChecked property has to be binded with a general Property which has INotifyPropertyChanged, NOT an ICommand type.
In order to bind with Command, the easiest workaround is to use Click(or Checked) event with xaml.cs Code-behind works.
In XAML, as below.
<ToggleButton x:Name="recordButton"
Checked="OnRecordButton_Checked"
IsChecked={Binding IsRecording} />
In Code-behind, as below.
private void OnRecordButton_Checked(object sender, RoutedEventArgs e)
{
if (recordButton.IsChecked.GetValueOrDefault())
{
// Do your own logic to execute command. with-or-without command parameter.
viewModel.ToggleRecordCommand.Execute(null);
}
}
And, In ViewModel (assumption), as below.
// Property for toggle button GUI update
public bool IsRecording{
get{ return _isRecording;}
set{
_isRecording = value;
NotifyPropertyChanged("IsRecording");
}
}
public ICommand ToggleRecordCommand{
// Your command logic.
}
IsChecked is a bool? property and will likely not work if you pass it an ICommand. Source code
If you'd like to see this supported, please raise an issue on the project site and we can discuss it further.

How to juggle multiple DataContexts on one XAML window?

I have an class, let's refer to it as SomeClass. SomeClass implements INotifyPropertyChanged and this is coded as follows:
public class SomeClass
{
.
.
.
private bool _isDirty;
public bool IsDirty
{
get { return this._isDirty; }
set
{
this._isDirty = value;
this.NotifyPropertyChanged("IsDirty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I have a form that uses an instance of SomeClass, called instanceOfSomeClass
This property all fires correctly but onto the main issue which is where I have a Save button bound to that property viz.
<Button Content="Save" Height="23" Name="btnSave" IsEnabled="{Binding Path=IsDirty}" Width="60" Margin="10, 10" HorizontalAlignment="Right" Click="btnSave_Click" />
A combo box SelectionChanged event is supposed to change that property is defined as follows:
<ComboBox Name="cboListOfUsers" ItemsSource="{Binding}" SelectionChanged="cboSomeCombo_SelectionChanged"/>
(I have removed parts of the combo box definition that are not pertinent to the question such as styles etc)
Critically the DataContext of the combo box is not set to the instanceOfSomeClass, rather a List of a custom class.
The SelectionChanged event fires and I have code that looks like this:
instanceOfSomeClass.IsDirty = true;
instanceOfSomeClass.User = (ApplicationUser) cboSomeCombo.SelectedItem;
This runs and although it does change the property and raise the appropriate notification it doesn't enable the command button. I surmise that this is because the DataContext for the combo is different to the DataContext for the command button
I've tried changing the DataContext in the SelectionChanged event but this just results in nothing being selected in the combo (the Save button is enabled though!)
Any help would be greatly appreciated
I surmise that this is because the DataContext for the combo is
different to the DataContext for the command button
no i don't think so. You could try that by not binding the ItemsSource directly to the DataContext instead using a member on the datacontext or using RelativeSource, ElementName, directly specifying the source or another binding syntax. I greatly suggest to use a collection from a property and not set the collection as the datacontext (personally i think thats really bad style, {Binding} should only be used very rarely and i use it only when ContentControls are involved).
Check the Datacontext on the button, use snoop for that it helps greatly by finding bugs like these. Make sure the property is REALLY raised, i can't count how many times we didn't step in the actual NotifyPropertyChanged where the bug was.
Make sure your button doesn't use a command sowhere because commands change the IsEnabled property in some ways.
Make sure nobody is overwriting the IsEnabled property, like Triggers, Animations etc.
Check the output for binding errors or warnings, enable them if you use vs10.
I will update my answer if you can provide more info, was just to much for a comment.

Categories