I have a two-way binding on the UIElement RenderTransformProperty. Connecting from the ViewModel to the View works fine, and the ViewModel can read an updated value from the View, but the ViewModel is not aware when the property in the View changes. I know I could create my own DependencyProperty and shuttle the value but it seems wrong. How do I properly notify the ViewModel when the View's RenderTransformProperty changes.
In the View:
var renderTransformBinding = new Binding
{
Path = new PropertyPath("SymbolTransform"),
Mode = BindingMode.TwoWay
};
SetBinding(RenderTransformProperty, renderTransformBinding);
In the ViewModel (using MVVM Light):
private Transform _symbolTransform;
public Transform SymbolTransform
{
get { return _symbolTransform; }
set
{
if (Set(() => SymbolTransform, ref _symbolTransform, value))
{
_isDirty = true;
}
}
}
I was mistaken. The ViewModel is being notified of the change - i.e. the setter is being called. For some reason _symbolTransform and value are equal so if (Set(() => SymbolTransform, ref _symbolTransform, value)) is returning false. No idea how _symbolTransform is (apparently) being updated before the setter is called.
Related
I have a view model that I bind to my view via
public MyViewModel viewModel = new MyViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = viewModel;
}
MyViewModel implements the INotifyPropertyChanged interface with the standard
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
MyViewModel has a string member called Status which is defined with
private string _status = string.Empty;
public string Status
{
get => _status;
set
{
if (_status != value)
{
_status = value;
OnPropertyChanged();
}
}
}
The Status property is used in my view like so:
<TextBlock Text="{Binding Status}"></TextBlock>
As my application runs, the value of Status periodically changes. I can see that OnPropertyChanged fires correctly in each case, however the view only changes some of the time. I have also noticed (by logging to the console when a property changes) that the property sometimes changes (and fires the change event) but then takes a few seconds before the View updates randomly.
I believe this has something to do with the binding. I can't afford to manually trigger a bindingExpression.UpdateTarget() manually, as the property changes when certain things happen under the hood of the view model (outside of the influence of the view).
I expected the view to update as soon as the binded property changed, but it did not.
I have tried changing the binding mode to TwoWay as well as binding the View Model within the XAML but nothing changes. There must be something I am missing. Appreciate any help.
My binding is not updating the View in real-time when an OnPropertyChanged is called. I am able to set breakpoints to see the value being changed in the View Model. The View eventually updates when I make another selection, but not in real-time. I believe I know what the problem is, but I am struggling with fixing the problem. I think the problem is that I am calling the CreateChartNode method that is creating a new instance.
In my View Model, it looks like this:
public class ChartObjectVM : INotifyPropertyChanged
public ChartNode
{
get
{
return CreateChartNode();
}
}
private string fullText;
public string FullText
{
get
{
return this.fullText;
}
set
{
this.fullText = value;
OnPropertyChanged("FullText");
}
public ChartNode CreateChartNode()
{
ChartNode newChartNode = new ChartNode();
And in my ViewModel I am doing:
public void CreateNode()
{
ChartObjectVM cObjectVM = new ChartObjectVM();
ChartNode = cObjectVM.CreateNode();
}
My binding in the View looks like:
{Binding Path = SelectedChartObject.UserObject.FullText, UpdatedSourceTrigger=PropertyChange, Mode=TwoWay}
Where SelectedChartObject is the currently selected ChartObjectVM.
Like I have said, I am pretty sure the problem is the CreateNode() method being called. I think there are two instances under the hood but I can't figure out why.
I have a feeling that my view isn't being updated because the NotifyPropertyChanged event is firing prior to the UI being constructed but I don't know how to overcome this.
I am not really posting code for analysis because I know that the databindings work. They just fail during the construction of the page.
I am strictly posting it so you can get an idea of what I am talking about.
public Obj1 SelectedObj1
{
get { return _SelectedObj1; }
set { _SelectedObj1 = value; NotifyPropertyChanged("SelectedObj1"); }
}
public Obj2 SelectedObj2
{
get { return _SelectedObj2; }
set { _SelectedObj2= value; NotifyPropertyChanged("SelectedObj2"); }
}
public Obj3 SelectedObj3
{
get { return _SelectedObj3; }
set { _SelectedObj3 = value; NotifyPropertyChanged("SelectedObj3"); }
}
Inside my constructor
public constructor(){
BuildFakeData();
SelectedObj1 = observableCollection[0];
SelectedObj2 = SelectedObj1.obj2s.Count > 0 ? SelectedObj1.obj2s[0] : null;
SelectedObj3 = SelectedObj2.obj3s.Count > 0 ? SelectedObj2.obj3s[0] : null;
}
My question is, when you are doing MVVM, if you set bound properties in the constructor, say for a DataGrid selected Row, will it populate or is it failing because the XAML isn't built yet?
Here is where the datacontext is created in the view
<Window.Resources>
<vm:ViewModel x:Key="viewModel"/>
</Window.Resources>
<Grid
DataContext="{StaticResource viewModel}">
Here is where I am setting the selected item for the grid
<igWPF:XamDataGrid
ActiveDataItem="{Binding SelectedObj1}"
DataSource="{Binding observableCollection}"
If your view doesn’t exist yet when the view model is created, then of course, the view isn’t listening yet when your properties update. However, when the view is then created and the view model is assigned as its data context, then the view will automatically load the values from the view model (its data context).
So, INPC shoulnd’t be an issue there at all. You could create properties without INPC in your example and have it work (since the values are already set in the constructor).
I've created a property "IsLoading" for my main view model. The idea is that a progressbar is displayed whenever this property is set to true. So far so good
The catch is, that I have a command, that calls another viewmodel (the code is there because it's a functionality from another page, but I want to be able to shortcut it as well from my main viewmodel)
So, I went ahead and modified the main property to something like this :
public const string IsLoadingPropertyName = "IsLoading";
private bool _isLoading;
public bool IsLoading
{
get
{
return _isLoading || ((ViewModelLocator)Application.Current.Resources["Locator"]).SettingsViewModel.IsLoading;
}
set
{
if (value != _isLoading)
{
_isLoading = value;
RaisePropertyChanged(IsLoadingPropertyName);
}
}
}
and the xaml
<shell:SystemTray.ProgressIndicator>
<shell:ProgressIndicator IsIndeterminate="true" IsVisible="{Binding Main.IsLoading, Source={StaticResource Locator}}" />
</shell:SystemTray.ProgressIndicator>
So, I'm saying that main view model is loading when there's something loading there, or if the settings view model is loading.
The problem is that the binding only works when setting the main view model's IsLoading property, it doesn't react when I set it in the inner IsLoading one. Both have the same property name "IsLoading". Shouldn't it be detected?
For example, in Main view model (just the execution of the command for simplicity) :
private void ExecuteRefreshCommand()
{
ViewModelLocator viewModelLocator = Application.Current.Resources["Locator"] as ViewModelLocator;
viewModelLocator.SettingsViewModel.GetCurrentLocationCommand.Execute(null);
}
and inside the settings view model :
public RelayCommand GetCurrentLocationCommand
{
get
{
Action getLocation = () =>
{
if (!NetworkInterface.GetIsNetworkAvailable())
{
return;
}
var watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.Default);
watcher.PositionChanged += WatcherPositionChanged;
IsLoading = true; // settings view model "IsLoading" propertychanged raising property
watcher.Start();
};
return new RelayCommand(getLocation);
}
}
You're looking at the MainViewModel's isLoading property to determine whether to show the progressbar or not. Silverlight uses the NotifyPropertyChanged event to determine when it should reevaluate a certain property. When setting either the SettingsViewModel's IsLoading property- or the MainViewModel's property, you only raise the changedEvent for that ViewModel. You should raise the ChangedEvent for both.
A modified setter example could be (depending on the exposed methods)
set
{
if (value != _isLoading)
{
_isLoading = value;
RaisePropertyChanged(IsLoadingPropertyName);
((ViewModelLocator)Application.Current.Resources["Locator"]).SettingsViewModel.RaisePropertyChanged(IsLoadingPropertyName);
}
}
Note that many MVVM frameworks offer a functionality called Messaging which is ideal to do cross ViewModel communication without creating the strict dependency you created right now. Alternatively you can use a globally consumed IsLoading property.
I've got DataGrid bound to an ObservableCollection<> in its ViewModel:
<DataGrid ItemsSource="{Binding Path=Data}" SelectedItem="{Binding Path=CurrentItem}" />
ViewModel:
public ObservableCollection<TestModel> Data { get; set; }
private TestModel _currentItem;
public TestModel CurrentItem
{
get { return _currentItem; }
set
{
_currentItem = value;
RaisePropertyChanged("CurrentItem");
}
}
Now what I want is, that the DataGrid will preselect the first Row right on Form-startup. So I put the following in my test-code inside the constructor:
Data = new ObservableCollection<TestModel>
{
new TestModel() { Property1 = Guid.NewGuid().ToString() },
new TestModel() { Property1 = Guid.NewGuid().ToString() },
new TestModel() { Property1 = Guid.NewGuid().ToString() }
};
CurrentItem = Data[0];
The data is displayed but the first row isn't selected by the grid. Even if I set the binding to TwoWay, it won't work.
If I remove the SelectedItem-binding in XAML and add the following in Code-behind, it works well:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var m = this.DataContext as MainViewModel;
grid.SelectedItem = m.CurrentItem;
}
What's happening is that your VM is being assigned to the data context before the window is initialized and therefore never receives the message that the CurrentItem has changed because it was changed before it loaded.
What I do, is pass in the view model into View's constructor and set it after the InitializeComponent() function is called. Because I am using Prism I am using inversion of control (IOC) and Prism knows to input my VM into the constructor. If you are instantiating your view and view model yourself, you can just pass in the view model. I ran into the same issue and this works.
public MyView(IMyVM viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
By the way, in working with MVVM, I see no reason not to pass in the ViewModel into the View because the view is dependent on it anyway. I know some people feel differently but it is either this or you will have to so some type of refresh of the datacontext in the Window_Loaded event.