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).
Related
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 view in wpf, that has a range of different boxes, for example, First/Last Name (TextBox), Date of Birth (DatePickers), Marital Status (ComboBox) etc.
What I want to be able to do, is get the text entered into the TextBoxes and show them in a TextBlock on a seperate view.
I have added properties for all the corresponding items in there retrospective ViewModels, but from there on in, I'm unsure on how to implement this any further.
Other questions I have looked at aren't very clear or easy to follow.
You simply need to set the DataContext of both views to the same instance of your ViewModel.
<StackPanel>
<Local.EditableView DataContext={Binding Person} />
<Local.ReadOnlyView DataContext={Binding Person} />
</StackPanel>
There are multiple ways to achieve this. I assuming you are not using any framework like Caliburn.Micro.
Simple Approach:
Create a global static class that shares information across multiple ViewModel.
Now, from the first ViewModel, update the static class property using the ViewModel property setter, something like
private string _lastName;
public string LastName{
get{
return _lastName
}
set{
_lastName = value;
SharedClass.LastName = value;
}
}
Now access this shared class from the other ViewModel.
One approach is to use a Mediator to communicate between view models.
You would typically register a "target" view model -- "colleague" -- with the mediator for certain operations that the view model is interested in and provide a callback action for what is supposed to happen when that operation occurs. Then the other view model -- the one performing the operation that the target is interested in -- would notify the mediator when the operation happens, and the mediator would then perform the associated action on all the colleagues that are registered for that operation.
Here's an example of a mediator:
static class Mediator
{
private static Dictionary<string, List<Action<Object>>> _tokenCallbacks
= new Dictionary<string, List<Action<object>>>();
internal static void Register(string token, Action<Object> callback)
{
token = token.ToLower();
if (_tokenCallbacks.ContainsKey(token))
{
var l = _tokenCallbacks[token];
var found = false;
foreach (var existingCallback in l)
{
if (existingCallback.Equals(callback))
{
found = true;
break;
}
}
if (!found) l.Add(callback);
}
else
{
var l = new List<Action<Object>>(new[] { callback });
_tokenCallbacks.Add(token, l);
}
}
internal static void NotifyColleagues(string callbackToken, Object args)
{
callbackToken = callbackToken.ToLower();
if (_tokenCallbacks.ContainsKey(callbackToken))
_tokenCallbacks[callbackToken].ForEach((x) => x(args));
}
}
Those views and their view models should reference a shared model of the data.
So that when data is entered in one view, its view model updates the model and the model update triggers a update in the other view model and finally in the other view.
If you have lots of cross viewmodel communication, use Messenger. That acts as a mediator and simplified lots of issues like this. You can either implement one yourself or use either the MVVM light or Prism toolkits.
I'm using the following code which is copy pasted from the main window which was working as expected ,
I have created View which is user control and put the code of the
code from the main window XAML
In the View model I put reference for the User model
In the user control I put the code for from the the main window which
is related to the event handlers for example the
DropText_PreviewDragEnter & listbox_SelectionChanged
Currently I have 2 issues in the User Control which Im not sure how to overcome...
1. Errors in the user control for all the occurrence of the ListBox (for example from listbox_SelectionChanged ystem.Windows.Controls.ListBox.SelectedItems.Count > 0 . the Selected items are marked at red with the following error
"cannot access non-static property SelectedItems item source in static context". ,not sure what is the reason since in the main window it was the same as static.
2. Since I have copied the code from the main window there is references to user object in the user controlwhich I believe is not acceptable in MVVM ,how should I change it ? for example
var mySelectedItem = System.Windows.Controls.ListBox.SelectedItem as User;
or
bool remove = _UsersList.Remove((User) System.Windows.Controls.ListBox.SelectedItem);
Here is the code.
I will appreciate your help !
The view model
public partial class ModelView : UserControl
{
private const string DRAG_SOURCE = "DragSource";
public ModelView()
{
InitializeComponent();
DataContext = new ModelView();
}
//Drag Over from text box to List box
private void ListBox_PreviewDrop(object sender, DragEventArgs e)
{
object dragSource = e.Data.GetData(DRAG_SOURCE);
if (dragSource != null && dragSource is TextBox)
{
(dragSource as TextBox).Text = String.Empty;
}
if (!String.IsNullOrEmpty(e.Data.GetData(DataFormats.StringFormat).ToString()) && dragSource is TextBox)
{
_UsersList.Add(new User {Name = e.Data.GetData(DataFormats.StringFormat).ToString()});
}
else
{
e.Handled = true;
}
}
}
}
The Xaml is
<TextBox x:Name="name1"
AcceptsReturn="True"
AllowDrop="True"
PreviewDragEnter="DropText_PreviewDragEnter"
PreviewDrop="DropText_PreviewDrop"
PreviewMouseDown="DropText_PreviewMouseDown"
HorizontalAlignment="Left" Height="20" Margin="360,70,0,0" TextWrapping="Wrap" Text=""
VerticalAlignment="Top" Width="70"/>
....
The model view
internal class ModelView
{
private ObservableCollection<User> _UsersList = new ObservableCollection<User>();
public ObservableCollection<User> UserList
{
get { return _UsersList; }
}
public void InitUsers()
{
_UsersList.Add(new User {Name = "fff"});
//Sort the User collection
ICollectionView usersView = CollectionViewSource.GetDefaultView(_UsersList);
usersView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
}
}
You already have two answers explaining why the first issue happend in the previous question. And follwoing points are what #Will said in comment as a mess in MVVM implementation that I can see in your codes :
UsersList in the model view is a Model as in Model-View-ViewModel.
And the model view it self is a ViewModel as in Model-View-ViewModel
Then what you call view model is actually a View in Model-View-ViewModel point of view. It inherits UserControl and UserControl is a view, no difference from Window or Page, etc. They're all View. And even if we agree to call it view model, then it violated MVVM principle everywhere, because view model shouldn't have reference to View/UI control object.
Not directly answering your question, but I hope you get a better prespective on MVVM pattern.
#phil correctly noted that you can't access the ListBox like this:
System.Windows.Controls.ListBox
What he failed to mention is that you shouldn't access a ListBox at all if you're using MVVM. Clearly you're not using MVVM now, but if you want to, then I would recommend that you read up on it so that you can get the full benefit from it. Just having a view and a view model does not mean that you're using MVVM.
In MVVM, we manipulate data, not UI controls. Therefore, you need to create a SelectedItem property in your view model and bind that to the ListBox.SelectedItem property and then you'll always have access to the item that is selected:
public User SelectedItem { get; set; } // Implement INotifyPropertyChanged here
...
<ListBox ItemsSource="{Binding YourCollection}" SelectedItem="{Binding SelectedItem}"/>
Now you can do something with the selected item like this:
string selectedItemName = SelectedItem.Name;
you have to access your listbox by
yourListBoxName.SelectedItems.Count > 0
you can't access it by
System.Windows.Controls.ListBox.SelectedItems.Count
same for
var mySelectedItem = System.Windows.Controls.ListBox.SelectedItem as User;
use the following instead
var mySelectedItem = yourListBoxName.SelectedItem as User;
I have created an application which uses WPF and MVVM following this article from CodeProject.
I have a view, TVSeriesView, which has a TVSeriesViewModel. These two are connected using a DataTemplate which is done following the article.
<DataTemplate DataType="{x:Type Implementation:TVSeriesViewModel}">
<TVSeriesLibrary:TVSeriesView />
</DataTemplate>
The idea is to pass my model, the TVSeries, to this ViewModel as I have a property named TVSeries in the ViewModel. When this property is set, I will populate other properties such as Title, Cover and so on. These properties are meant to be binded to controls in the view.
public class TVSeriesViewModel : ViewModelBase, ITVSeriesViewModel
{
private TVSeries _tvSeries;
private string _title;
private ImageSource _cover;
public TVSeries TVSeries
{
get
{
return this._tvSeries;
}
set
{
this._tvSeries = value;
}
}
public string Title
{
get
{
return this._title;
}
set
{
this._title = value;
OnPropertyChanged("Title");
}
}
public ImageSource Cover
{
get
{
return this._cover;
}
set
{
this._cover = value;
OnPropertyChanged("Cover");
}
}
}
First and foremost, does this sound like the right way to do it?
Next, does anyone know how to pass a parameter (a TVSeries object) to the ViewModel when the TVSeriesView is shown?
And lastly, does anyone know how I can directly access resources in the view? For example if I don't want to use data binding but instead want to set the image directly like this:
myImage.ImageSource = myImageSource
The View and ViewModel together are one of the possible representations of the Model.
You can pass a repository handle which would be eventually responsible for data access or
Concrete/abstract object of Model through Dependency Injection via Constructor or
Dependency Injection, via property/method or
In more crude way you can write a DB access code in your VM (obviously it's not suggested.)
I would prefer as the order given here. Your code is doing the third option.
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.