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.
Related
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
I'm working on a UWP application and I realized that the default UpdateSourceTrigger mode for the TextBox control, which is LostFocus, can't be changed when using a compiled binding.
This means that whenever I want the binding to update for a TextBox, I have to use all this repeated boilerplate:
<TextBox
Text="{x:Bind ViewModel.Title, Mode=TwoWay}"
TextChanged="TextBox_OnTextChanged"/>
private void TextBox_OnTextChanged(object sender, TextChangedEventArgs e)
{
ViewModel.Title = ((TextBox)sender).Text;
}
Now, this is not too bad, but having to remember to create the TextChanged handler every single time a TextBox is used is annoying and error prone.
This would work fine with a classic binding:
<TextBox Text="{Binding Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
But of course, here there would be the additional overhead of usinc classic bindings (involving runtime reflections, etc.).
Is there a way to get the same behaviour of UpdateSourceTrigger=PropertyChanged as well? I'd be completely fine with, say, writing a custom attached property that sets things up, as long as I can do everything I need directly from XAML, with no code behind involved.
Thanks!
UPDATE: (in response to Nico Zhu - MSFT's answer)
For my testing, it works well.
It doesn't for me, at all, As I said multiple times already, using UpdateSourceTrigger with x:Bind is just not possible. It doesn't compile, the property is shown in red in the XAML editor, it just isn't there. I really don't know where are you trying that, if you say it's working for you. I'm currently targeting 17763 as minimum and I can 100% guarantee that that does not work.
Compiled Binding is used with the {x:Bind} syntax as opposed to the {Binding} syntax of Classic Binding.
I'm well aware of the difference, I've already mentioned this multiple times, both in my original question here (with code snippets too) as well as in my comments.
It still uses the notifying interfaces (like INotifyPropertyChanged) to watch for changes
As I said, I'm aware of this too. But again, as from this question, this isn't the problem here at all. The issue is not with updates from the viewmodel to the bound property, but from the bound property (TextBox.Text in this case) to the viewmodel.
{x:Bind} is by default OneTime compared to {Binding} which is OneWay. so you need to declare bind Mode OneWay or TwoWay for {x:Bind}.
I'm sorry, but I have to say at this point I'm starting to wonder if you've actually read my initial question at all. I'm aware of this, and in fact you can see in both my original code snippets that I had already used the explicit Mode=TwoWay property in both my bindings.
And once again, this was not what the question was about, at all.
To reiterate: the issue here is that the TextBox.Text property defaults to the LostFocus trigger, and that the UpdateSourceTrigger property is not available for compiled bindings. So I'd like to know if there's a way to achieve the same, with a compiled binding, in XAML-only, without having to manually create a TextChanged handler every single time (and if not, if you plan to eventually add the UpdateSourceTrigger property to compiled bindings too).
Side note: I didn't mean to sound disrespectful here, and I hope we've now solved the existing misunderstandings with my question.
UPDATE #2: turns out the issue was causing by the ReSharper plugin, which was marking the UpdateSourceTrigger property as error in compiled bindings.
I've opened an issue for that here: https://youtrack.jetbrains.com/issue/RSRP-474438
Please check UpdateSourceTrigger documentation.
The default UpdateSourceTrigger value is Default. And
using default behavior from the dependency property that uses the binding. In Windows Runtime, this evaluates the same as a value with PropertyChanged. If you used Text="{x:Bind ViewModel.Title, Mode=TwoWay}", the Title will be changed when text changes. we have not need modify the viewmode in TextChanged even handler.
The premise is that we need implement INotifyPropertyChanged like the follow.
public class HostViewModel : INotifyPropertyChanged
{
private string nextButtonText;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public HostViewModel()
{
this.NextButtonText = "Next";
}
public string NextButtonText
{
get { return this.nextButtonText; }
set
{
this.nextButtonText = value;
this.OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
For more detail please refer Data binding in depth document.
Update
<TextBox Text="{x:Bind Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> doesn't compile at all, as I said the UpdateSourceTrigger property isn't available at all when using a compiled binding.
For my testing, it works well. Compiled Binding is used with the {x:Bind} syntax as opposed to the {Binding} syntax of Classic Binding. It still uses the notifying interfaces (like INotifyPropertyChanged) to watch for changes but {x:Bind} is by default OneTime compared to {Binding} which is OneWay. so you need to declare bind Mode OneWay or TwoWay for {x:Bind}.
Xaml
<StackPanel Orientation="Vertical">
<TextBox Text="{x:Bind Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{x:Bind Title, Mode=OneWay}" /> <!--declare bind mode-->
</StackPanel>
Code behind
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _title;
public string Title
{
get
{
return _title;
}
set
{
_title = value;
OnPropertyChanged();
}
}
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();
}
I have a ViewModel with a complex property type and want to bind my view to a nested property of this object.
My ViewModel is implementing INotifyPropertyChanged (or do be extact BaseViewModel is implementing it). The class of the parent property is not implementing INotifyPropertyChanged.
The class Car is not implementing INotifyPropertyChanged. But I'm not changing the property Manufacturer, I change the MyCarProperty property, and so I expect that the OnNotifyPropertyChanged event will trigger the value update?
When I'm updating the value of the parent property, the nested property is not updating. Can you tell me how can I implement this functionality?
ViewModel
public class ViewModel : BaseViewModel
{
private Car _myCarProperty;
public Car MyCarProperty
{
get { return _myCarProperty; }
set
{
if (value == _myCarProperty) return;
_myCarProperty = value;
OnPropertyChanged();
}
}
}
Binding in the View
<TextBlock Text="{Binding Path=MyCarProperty.Manufacturer}" />
When I change the value of MyCarProperty the View does not update.
Thanks for any help!
Edit: OnPropertyChanged() implementation
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged
"The class Car is not implementing INotifyPropertyChanged. But I'm not
changing the property Manufacturer, I change the MyCarProperty
property, and so I expect that the OnNotifyPropertyChanged event will
trigger the value update?"
Nope, it won't trigger the value update a level down. Bindings don't listen to property changes for an entire path, they listen only to the object that they're bound to.
I see a couple options off the top of my head (in order of my preference when I run into this):
Bind to the car, not the sub property, and create a data template that displays what you want out of it.
Manually kick the binding by calling UpdateTarget on it's BindingExpression when you need to.
I know it looks like there's a lot more to learn on the data template route, but I promise you that data templates will prove vastly more powerful, scalable, maintainable, and useful than manually kicking bindings as you work more in WPF. (Also, once you understand them, I think they're actually less work than manually kicking bindings).
Good luck!
The accepted answer explains how to handle the case where a sub-property on a Binding Source is changed and you wish to update the view - which is not what the question is asking. WPF will in fact respond to changes from many levels down, so long as you are notifying changes for any properties being changed within the specified path.
As for this:
"The class Car is not implementing INotifyPropertyChanged. But I'm not changing the property Manufacturer, I change the MyCarProperty property, and so I expect that the OnNotifyPropertyChanged event will trigger the value update?"
WPF handles this already.
In your example, ViewModel is the Binding Source. When you set MyCarProperty (firing the NotifyPropertyChanged event), WPF will re-evaluate the Binding Target Value using the Binding Path for the new Binding Source object - updating your view with the new Manufacturer.
I have tested this with a simple WPF app - it also holds true for very deeply nested Paths:
https://pastebin.com/K2Ct4F0F
<!-- When MyViewModel notifies that "MyCarProperty" has changed, -->
<!-- this binding updates the view by traversing the given Path -->
<TextBlock Text="{Binding Path=MyCarProperty.Model.SuperNested[any][thing][you][want][to][try][and][access].Name}" />
I'm not an WPF expert, but I think it's because you've chosen the wrong path.
<TextBlock Text="{Binding Path=MyCarProperty, Value=Manufacturer}" />
update:
<TextBlock Text="{Binding Source=MyCarProperty, Path=Manufacturer}" />
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.