My Main window has a sidebar menu. When an item on the menu is clicked, I will render that item's page (UserControl) on a ContentControl. Here is what it looks like.
My MainViewModel
public MainViewModel()
{
SystemMenu = new List<SystemMenuViewModel>();
SystemMenu.Add(new SystemMenuViewModel("Dashboard", new Dashboard()));
SystemMenu.Add(new SystemMenuViewModel("Appointments", new Dashboard()));
SystemMenu.Add(new SystemMenuViewModel("Reports", new Reports()));
SystemMenu.Add(new SystemMenuViewModel("Configuration", new Configuration()));
}
private string _windowTitle = GlobalVariables.WindowTitleDefault;
private string _currentPage = "Dashboard";
public string WindowTitle
{
get { return _windowTitle; }
set
{
_windowTitle = value;
NotifyOfPropertyChange(() => WindowTitle);
}
}
public string CurrentPage
{
get { return _currentPage; }
set
{
_currentPage = value;
NotifyOfPropertyChange(() => CurrentPage);
}
}
public List<SystemMenuViewModel> SystemMenu { get; set; }
My SystemMenuViewModel
private string _name;
private object _content;
public SystemMenuViewModel(string name, object content)
{
_name = name;
Content = content;
}
public string Name
{
get { return _name; }
set { this.MutateVerbose(ref _name, value, RaisePropertyChanged()); }
}
public object Content
{
get { return _content; }
set { this.MutateVerbose(ref _content, value, RaisePropertyChanged()); }
}
public event PropertyChangedEventHandler PropertyChanged;
private Action<PropertyChangedEventArgs> RaisePropertyChanged()
{
return args => PropertyChanged?.Invoke(this, args);
}
My MainView on the rendering part
<ContentControl Content="{Binding ElementName=lstSystemMenu, Path=SelectedItem.Content}" />
My main problem is that I am just rendering the Content on my MainView without actually invoking or binding its ViewModel.
I am sure that there is something wrong on my implementation of the MVVM framework. Kindly enlighten me on what part did I go wrong and what's the best way to implement this one.
Have a look at this : https://msdn.microsoft.com/en-us/magazine/dd419663.aspx
Can't find the source zip, but the article has plenty of code samples.
What you need is to bind your ContentControl's Content property to a ViewModel/Model object, and use DataTemplates to create the correct page depending on the datacontext. The datatemplates just need to be stored in a ResourceDictionary, either the ContentControl's or some upper level control (or even the app). The DataTemplates must have a DataType set for this to work.
Also, as suggested in the comments on your question, the viewmodels shouldn't have a "content" property of type object. It looks like your "content" property is a view object or something. Can't know without you showing us more code.
The ViewModel should not reference any View object. But the View can reference ViewModel classes in the code-behind or in the XAML.
There are two ways to bind to the "current selection".
Either use the "current selected" info from the view list (SelectedItem for example), or add a property in the MainViewModel (ex: SelectedViewModel, and then bind the ContentControl to this property.
Related
I have a window that displays templates in a tree, these can be selected which updates a ListView with available fields in the template. All related operations until here are managed by the TemplateViewModel declared at windows level as:
<Window.DataContext>
<vm:TemplateViewModel/>
</Window.DataContext>
extract of the class:
public class TemplateViewModel : ViewModelBase,INotifyPropertyChanged
{
public FieldTypeViewModel FieldTypeView { get; }
public TemplateViewModel()
{
// Create additional view
FieldTypeView = new FieldTypeViewModel(this);
...
}
Each template field has an identifier and type which are still managed by this view (all working up to here).
Now depending on the type of the field a different page is to be displayed in a reserved window part (Frame). Also the type view model is a separate view model class FieldTypeView .
The FieldType object is created in the constructor of the TemplateViewModel and saved in the FieldTypeView property as it needs to be linked to this model for updating as field gets selected.
Both views used to implement the INotifyPropertyChanged interface but since the FieldTypeView is created by the view and not by the window defintion the notification event is not set, so I currently call the parent (TemplateViewModel) event for notification.
So I have a frame defined as:
<Frame DataContext="{Binding FieldTypeView}" Grid.Row="1" Content="{Binding CurrentFieldTypeSetupPage}"/>
public class FieldTypeViewModel : ViewModelBase
{
private TemplateViewModel _templateViewModel;
private TTemplateFieldType? _FieldType;
public TTemplateFieldType? FieldType
{
get { return _FieldType; }
set { _FieldType = value;
UpdateFieldType();
NotifyPropertyChanged("FieldType"); }
}
private Page? _CurrentFieldTypeSetupPage;
public Page? CurrentFieldTypeSetupPage
{
get { return _CurrentFieldTypeSetupPage; }
set { _CurrentFieldTypeSetupPage = value; NotifyPropertyChanged("CurrentFieldTypeSetupPage"); }
}
// Define property per type for easy data context access
public TTFTText? tfText { get; set; }
public TTFTDate? tfDate { get; set; }
//------------------------------------------------------------------------------
private void UpdateFieldType()
{
// Set the appropriate field type, and "null" the others
tfText = _FieldType as TTFTText;
tfDate = _FieldType as TTFTDate;
if (_FieldType != null)
{
CurrentFieldTypeSetupPage = _FieldType.GetSetupPage();
}
else
{
CurrentFieldTypeSetupPage = null;
}
}
//------------------------------------------------------------------------------
public void NotifyPropertyChanged(string prop)
{
_templateViewModel.NotifyPropertyChanged(prop);
}
//------------------------------------------------------------------------------
public FieldTypeViewModel(TemplateViewModel templateVM)
{
_templateViewModel = templateVM;
}
}
Every time the field selection changes the TemplateViewModel does set the FieldTypeView which gets the correct window for the current type and sets its CurrentFieldTypeSetupPage, which finally notifies the change via NotifyPropertyChanged("CurrentFieldTypeSetupPage"); which actually calls the TemplateViewModel's NotifyPropertyChanged method calling the event handler to notify the change.
Note that notification in the TemplateViewModel works for all its other fields, but the type page is never shown.
So the question is what I am doing wrong or what is the correct way to implement dynamic page changing in MVVM. My guess is that INotifyPropertyChange is not the correct way to go ?
I have a class as follows:
public class UserData : INotifyPropertyChnaged
{
public string strUserName;
public string UserName
{
get { return strUserName; }
set { SetProperty(ref strUserName, value); }
}
private string strPhoneNumber;
public string UserPhoneNumber
{
get { return strPhoneNumber; }
set { SetProperty(ref strPhoneNumber, value); }
}
private List<UserMailID> listUserMailID;
public List<UserMailID> ListOfUserMailID
{
get { return listUserMailID; }
set { SetProperty(ref listUserMailID, value); }
}
}
I'm accessing this class at some location and this class object I am binding to my view:
private UserData cActiverUser;
public UserData ActiverUser
{
get { return cActiverUser; }
set { SetProperty(ref cActiverUser , value); }
}
Suppose there is UserName field in my view; my data binding goes like this:
TextBox.Text="{Binding ActiverUser.StrUserName, UpdateSourceTrigger=PropertyChanged}
Binding works well, but I am unable to raise the property changed event when I change this user name field on my view. I have tried setting Mode="TwoWay" also.
In your XAML binding, try setting NotifyOnTargetUpdated=True, or if that doesn't work NotifyOnSourceUpdated=True.
I had the same issue with a project I did a while ago, and it was one of those that got it working.
Also on this line:
public string strUserName;
Should this be private? and you should be specifying the binding as UserName, not strUserName?
I believe the bindings are also case sensitive.
If you're using Prism, derive UserData and whatever class has UserData as its property from BindableBase, which implements INotifyPropertyChanged, but also lets you use SetProperty(...). I'm not even sure how you were able to get it to work before, but I suspect you're not showing us your original code, since you misspelled INotifyPropertyChanged -- INotifyPropertyChnaged.
Additionally, are you actually instantiating the object? Don't forget to do that.
Lastly, you're binding to ActiverUser.StrUserName, but UserData does not have a property StrUserName. It has UserName, which is what you want to bind to.
I'm implementing MVVM for a WPF application.
The ViewModels are created as follows:
ViewModel: base class from which all ViewModels override
MainTemplateViewModel: the 'Masterpage' ViewModel which contains a ViewModel property Current that contains the ViewModel to show
CustomerOverviewViewModel: an example of a view that can be placed in the MainTemplateViewModel.Current
The CustomerGridViewModel contains a Telerik GridView. I would like to show the number of items in the title of the MainTemplateViewModel. The GridView.Items.Count property implements the INotifyPropertyChanged so I would like to bind this property to ViewModel.RowCount (because the CustomerGridViewModel doesn't know it is part of the MainTemplateViewModel it cannot be bound directly to the TextBlock). I can in turn use ViewModel.NumberOfRecords to show the amount in the title.
How can I bind the Count property to a property in my ViewModel?
Edit
I'll describe the issue in more detail:
The list of objects shown in the grid is a binding from the ViewModel:
<telerik:RadGridView x:Name="CustomerGrid" ItemsSource="{Binding CustomerViewModels}">
</telerik:RadGridView>
When filtering the Grid in memory, the Telerik Grid automatically changes the GridView.Items.Count property (this does not mean the original list count is changed!). So if I can bind this property to a property in the ViewModel class, this would solve the problem.
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
private int numberOfRecords;
public int NumberOfRecords
{
get { return numberOfRecords; }
set { numberOfRecords = value; OnPropertyChanged(); }
}
}
MainTemplateViewModel.cs
public class MainTemplateViewModel : ViewModel
{
private ViewModel current = new MainOverviewViewModel();
public ViewModel Current
{
get { return current; }
set
{
if (current != value)
{
current = value; OnPropertyChanged();
}
}
}
}
CustomerOverview.xaml.cs
public partial class CustomerOverview : UserControl
{
public CustomerOverview()
{
InitializeComponent();
this.CustomerGrid.Items.CollectionChanged += ItemsCollectionChanged;
this.CustomerGrid.Loaded += CustomerGrid_Loaded;
}
void CustomerGrid_Loaded(object sender, RoutedEventArgs e)
{
/* METHOD 1 PROBLEM: the field to bind to in the MainTemplate is out of scope and accessing a view is not MVVM */
var binding = new Binding();
binding.Path = new PropertyPath("Items.Count");
binding.Source = CustomerGrid;
((MainWindow)this.ParentOfType<MainWindow>()).NumberOfRecords.SetBinding(TextBlock.TextProperty, binding);
}
void ItemsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
/* METHOD 2 PROBLEM: codebehind code should be in viewmodel */
((CustomerOverviewViewModel)this.DataContext).NumberOfRecords = CustomerGrid.Items.Count;
}
}
Instead of loading the data in your UserControl, just declare a DependencyProperty of the relevant type in it. You can then load the data in the main view model and simply data bind to it from the UserControl. You could do something like this simple example:
In CustomerOverview.xaml.cs:
public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items",
typeof(ObservableCollection<YourDataType>), typeof(CustomerOverview),
new PropertyMetadata(null));
...
In CustomerOverview.xaml:
<ListView ItemsSource="{Binding Items, RelativeSource={RelativeSource AncestorType={
x:Type YourPrefix:CustomerOverview}}}" ... />
...
In MainWindow.xaml (or whichever relevant view):
<YourPrefix:CustomerOverview
Items="{Binding SomeCollectionPropertyInMainTemplateViewModel}" ... />
...
In MainTemplateViewModel.cs (or whichever relevant view model):
public ObservableCollection<YourDataType> SomeCollectionPropertyInMainTemplateViewModel
{
get { return someCollectionPropertyInMainTemplateViewModel; }
set
{
someCollectionPropertyInMainTemplateViewModel = value;
NotifyPropertyChanged("SomeCollectionPropertyInMainTemplateViewModel");
NotifyPropertyChanged("NumberOfRecords");
}
}
public int NumberOfRecords
{
get { return someCollectionPropertyInMainTemplateViewModel.Count; }
}
Telerik Grid has two properties
Visible Count
TelerikGrid.Items.Count
Total Count
TelerikGrid.Items.TotalItemCount
In case this helps!
If i get you right, you want your master to show details of the child.
Your master should be able to know your child by your Current property.
If you are using MVVM correctly, the data bound to your grid comes from the child-ViewModel.
In that case, you have already have the itemscount in your child-ViewModel.
After this you can say in your Master something like
<Label Content="{Binding Current.NumberOfRows}"></Label>
According to this page you could wrap your source in a QueryableCollectionView
I am developing Windows Universal app. I have one GridView which has one textblock and a button. The gridview gets data of un-purchased objects from a service. The button is for purchasing particular object. So if user clicks on button that object is purchased & gridview gets refreshed to remove purchased item from it.
I am illustrating my requirement in simplified manner. I tried two ways, both are not working. Can you please suggest me solution regarding it.
First way I used is to inherit Model class with ViewModel class so I can access methods of ViewModel class, but it throws StackOverflowException in ViewModelBase at SetProperty<T> method.
P.S. - I don't want to migrate to any framework like MVVMLight, etc.
ViewModel.cs
public class ViewModel : ViewModelBase
{
public ViewModel()
{
DataCollection = new ObservableCollection<Model>();
for (int i = 1; i < 10; i++)
{
DataCollection.Add(new Model { Number = i });
}
}
private ObservableCollection<Model> _DataCollection;
public ObservableCollection<Model> DataCollection
{
get { return _DataCollection; }
set { this.SetProperty(ref this._DataCollection, value); }
}
}
Model.cs
public class Model : ViewModel
{
public RelayCommand<int> DeleteCommand { get; set; }
public Model()
{
DeleteCommand = new RelayCommand<int>((x) => DeleteNumber(x));
}
private void DeleteNumber(int x)
{
var obj = DataCollection.Where(varNum => varNum.Number == x).FirstOrDefault();
if (obj != null)
{
DataCollection.Remove(obj);
}
}
private int _Number;
public int Number
{
get { return _Number; }
set { this.SetProperty(ref this._Number, value); }
}
}
2nd way I keep that isolated, so I was not able to access the methods.
ViewModel.cs is same as above
Model.cs
public class Model : ViewModelBase
{
public RelayCommand<int> DeleteCommand { get; set; }
public Model()
{
DeleteCommand = new RelayCommand<int>((x) => DeleteNumber(x));
}
private void DeleteNumber(int x)
{
// How to access ViewModel's DataCollection property or
// a method which sets un-purchased objects in DataCollection property
}
private int _Number;
public int Number
{
get { return _Number; }
set { this.SetProperty(ref this._Number, value); }
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Well, in the first example you're getting a StackOverflowException because your ViewModel instantiates 9 Models each time - and since your Model is an extension of ViewModel, each one of those instantiates 9 more Models and an infinite recursion happens. That doesn't answer your main question, though :)
Your class names are confusing to me, because in MVVM a "Model" is simply a representation of the data and methods to manipulate it, whereas the ViewModel requests this data from the Model and presents it via publicly accessible properties that are retrieved from the View via binding. The View knows about the ViewModel, the ViewModel knows about the Model and the Model just knows about the data. In any case you shouldn't be binding directly from the View to the Model!
You'll want to house the RelayCommand in your ViewModel so your View can bind to it, and depending on what you want to happen when a user purchases an item (store it in a database, track this in another variable, simply remove from the view without doing anything else, etc.) you may or may not need to write additional logic for when this occurs. Generally you'll want the ViewModel to handle user input and update both the presentation object as well as notify the Model a change was made, if this is something your app requires. Think of it as the Model holds the actual data whereas the ViewModel only holds what the user sees.
Unfortunately, without knowing what you're trying to do in a little more detail it's hard to give more specific advice than this!
I have a list of items with id and description(i can introduce key-value collection instead if needed). What i need is control that binded to viewmodel id property, but shows description of corresponding item/pair on it. Closest example i know is combobox, where i set DisplayMemberPath and SelectedValue/SelectedValuePath, but i don't need dropdown. So is there any in-built control in Silverlight for this?
(of course i can code one myself, it's easy and I can even just put some logic for viewmodel to get pair i need and bind it's description to simple textblock)
Edit: To illustrate what funcionality i need i coded simple example class. It actually satisfies my needs, but i still want to know if i can use built-in control.
public class CollectionItemDisplayControl:TextBox
{
public CollectionItemDisplayControl()
{
IsReadOnly = true;
}
public string SelectedID
{
get { return (string)GetValue(SelectedIDProperty); }
set { SetValue(SelectedIDProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedID. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedIDProperty =
DependencyProperty.Register("SelectedID", typeof(string), typeof(CollectionItemDisplayControl), new PropertyMetadata(new PropertyChangedCallback(OnSelectedIDChangedStatic)));
private static void OnSelectedIDChangedStatic(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CollectionItemDisplayControl originator = d as CollectionItemDisplayControl;
if (originator != null)
{
originator.OnSelectedIDChanged(e);
}
}
private void OnSelectedIDChanged(DependencyPropertyChangedEventArgs e)
{
string description = String.Empty;
string value = e.NewValue as string;
if (value != null)
{
foreach (var item in _items)
{
if (item.UniqueID == value)
{
description = item.Description;
break;
}
}
}
Text = description;
}
private IDataCollection _viewModel;
public IDataCollection ViewModel
{
get { return _viewModel; }
set
{
_viewModel = value;
if (_viewModel != null)
{
_items = _viewModel.Items;
}
}
}
private ObservableCollection<IUnique> _items = new ObservableCollection<IUnique>();
}
ItemClass contains two properties: ID and Description. I can place this control on the page, bind Items, and one-way bind SelectedID.
Edit 2: well i didn't make SelectedID DependencyProperty so binding won't work, but i will fix it right away
Edit 3: first snippet was sloppy and didn't work properly, so i fixed it.
If I understood properly,
You just need the right binding implemented.
(you do need a list? not just a single item, even if single it's similar just any control)
Bind the list to e.g. ItemsControl.
Set ItemsSource to your list of items
Then override ToString on your Item providing it's 'yours' really. If not you can make your own wrapper.
Within ToString output whatever is presenting your item, e.g. description.
That's a quickest solution, you can also make item template as you want.
EDIT:
well just put everything in the view model and bind to it - the TextBox, i.e.
Text={Binding SelectedText}
e.g.
...in your view model add SelectedText and SelectedID (and Items if needed) - properly do OnPropertyChanged.
Set SelectedID from view model or if 'bound' from another control that may change it.
Within set for SelectedID set the SelectedText.
No need for a control for things like that, it's all data binding really.