Is it correct to change model instances in runtime? My control was bound to first instance, but during the program execution I d like to bind them to another instance.
somewheere in ViewModel class :
//ViewDefault - already initialized
// View - will be ready later
public string TextProperty
{
get
{
if (View != null)
{
return View.Model.text;
} return ViewDefault.Model.text;
}
set
{
if(View != null)
{
//.. logic with View.Model.text
}else{
// logic with ViewDefault.Model.text
}
RaiseOnPropertyChanged("TextProperty");
}
The question is - what I must do to notify my View that a binding content is changed?
<Setter Property="Text" Value="{Binding MyViewModel.TextProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
*ViewDefault.Model and View.Model have one type
In my point of view a ViewModel links a View with a Model so if I had to change the Model I would create a new ViewModel and attach it to the View's DataContext .
The view should be created by someone (a factory preferably) and the one creating the view should be creating the datacontext too and attaching it doing
View view = new View();
view.DataContext = new ViewModel();
Related
I have a prism app where I am instantiating multiple views in the code-behind of a parent view based on a property in the viewmodel
public I2CNavigatorView()
{
InitializeComponent();
var viewModel = (I2CNavigatorViewModel) DataContext;
for (int i = 0; i < viewModel.NumberOfI2C; i++)
{
var i2CTabItem = new TabItem
{
Header = "I2C " + i,
Content = new I2CView(i)
};
NavigatorTabs.Items.Add(i2CTabItem);
}
}
and I need to pass the an index to the viewmodel of each child view, so my current solution is to pass the index to the view as a parameter when instantiating the view and setting a variable in the its viewmodel
public I2CView(int currentI2CIndex)
{
InitializeComponent();
var viewModel = (I2CViewModel) DataContext;
viewModel.CurrentI2CIndex = currentI2CIndex;
}
but I am not quite satisfied with the solution as the data flow path is: parent view -> child view ->
child viewmodel, while I need it to be: parent view -> child viewmodel
so I was thinking "Is there a way to pass the data directly to the view model when instantiating the view?"
Please advice,
Thanks in advance
I would start from the view model:
internal class I2CNavigatorViewModel
{
public IReadOnlyCollection<I2CViewModel> MyItems { get; }
}
<TabControl ItemsSource="{Binding MyItems}">
<TabControl.ContentTemplate>
<DataTemplate>
<I2CView />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Then just initialize MyItems with the child view models, created with all the parameters you need.
Rant: a class doesn't want to have its name start with I, because it doesn't like to be mistaken for an interface!
I have a main View with nested child Views. I have a main VM that holds instances of the child VMs.
At some point, the main VM needs to interact with child.
In the main VM. I defined the child ViewModel as
public ChildViewModel VmChild
{
get
{
if (this.vmChild == null)
this.vmChild = new ChildViewModel();
return this.vmChild;
}
set
{
if (this.vmChild != value)
{
this.vmChild = value;
this.OnPropertyChanged("VmChild");
}
}
}
In the main View. I have
<StackPanel Orientation="Vertical" HorizontalAlignment="Left" >
<localViews:ChildView DataContext="{Binding VmChild}"> </localViews:ChildView>
</StackPanel>
In the child view code behind.
public ChildViewModel ViewModel
{
get
{
if (this.vmChild == null)
this.vmChild = new ChildViewModel();
return this.vmChild;
}
set
{
if (this.vmChild != value)
{
this.vmChild = value;
}
}
}
And
private void InitializeViewModel()
{
if (this.DataContext is ChildViewModel)
{
this.ViewModel = this.DataContext as ChildViewModel;
}
else
{
this.DataContext = this.ViewModel;
}
}
My question is my code works out. However it has an exception when I open the main view.
The exception is NullReferenceException was thrown on "ChildView": Cannot create an instance of "ChildView". The error line is at this.vmChild = new ChildViewModel();
I think that I defined the view model instance twice to cause it. In the main View Model I already define the child vm instance. In code behind of the child view, I defined it again. But I don't know how to fix it.
EDIT:
You are trying to 'fix' a null value scenario that shouldn't occur or should be left alone.
Remove both the pieces of the child's code-behind and if you really need a ViewModel property (is this for UWP?), use this:
// not normally needed in WPF/MVVM
public ChildViewModel ViewModel { get { return DataContext as ChildViewModel; } }
The Child View is getting a ViewModel, it shouldn't create one.
In main view replace
DataContext="{Binding VmEChild}
with
DataContext="{Binding VmChild}
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'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;
Can someone explain how the View and ViewModel are connected? I can't find anywhere the xaml or the xaml.cs for the View that references the ViewModel, nor anything in the ViewModel.cs file that references the View, yet they are somehow connected, and binding members from the ViewModel to the View work.
Also, in the constructor of each, there is only the InitializeComponent for the View and a basic constructor for the ViewModel (no declaration/definition of the View).
Thanks!
There are various options here.
Something has to set the View's DataContext to be an instance of the ViewModel. There are lots of options here:
This can be done directly in xaml (the View just instances the ViewModel directly).
This can be done in the View's constructor (this.DataContext = new MyViewModel();)
This can be handled via a DataTemplate
A "coordinating" class can wire these together (ie: a separate "presenter" class can construct both and set the DataContext appropriately)
The most common are to either have the View define the VM in the xaml (View-first), or to have everything based from a ViewModel-centric point of view, and have WPF automatically create the View based on the bound VM (ViewModel-first).
The former approach is what's used by a lot of toolkits, such as MVVM Light. The latter approach is what I used in my MVVM blog series, and used by some other toolkits.
A "clean" way for connecting the views to the view-models would be...
When you create the views, for each view, set its DataSource to its view-model:
E.g.
public class App
{
private void OnAppStart()
{
var model = new MainModel();
var vm = new MainVM();
var view = new MainWindow();
vm.Model = model;
view.DataSource = vm;
view.Show();
}
}
When the model you are viewing changes, update the VM:
public class MainVM
{
private void OnSelectedModelItemChanged()
{
this.SelectedItem = new ItemVM();
this.SelectedItem.Model = this.SelectedModelItem;
}
}
And use data templates to make view select the correct sub views for each VM.
The view contains an object of the view model class in the xaml.
The InitializeComponent function creates all the controls on the page, sets styles, etc.
As others have already shown, there are multiple options. Of course, whenever you hear of multiple options you have to wonder what are the advantages and disadvantages of each. Well, it just so turns out that all of them have major disadvantages except one.
The following approach involves no external libraries, no additional housekeeping classes and interfaces, almost no magic, and is very flexible because you can have viewmodels that contain other viewmodels, and you get to instantiate each one of them, so you can pass constructor parameters to them.
For the viewmodel of the main window:
using Wpf = System.Windows;
public partial class TestApp : Wpf.Application
{
protected override void OnStartup( Wpf.StartupEventArgs e )
{
base.OnStartup( e );
MainWindow = new MainView();
MainWindow.DataContext = new MainViewModel( e.Args );
MainWindow.Show();
}
}
For all other viewmodels:
This is in MainViewModel.cs:
using Collections = System.Collections.Generic;
public class MainViewModel
{
public SomeViewModel SomeViewModel { get; }
public OtherViewModel OtherViewModel { get; }
public Collections.IReadOnlyList<string> Arguments { get; }
public MainViewModel( Collections.IReadOnlyList<string> arguments )
{
Arguments = arguments;
SomeViewModel = new SomeViewModel( this );
OtherViewModel = new OtherViewModel( this );
}
}
This in MainView.xaml:
[...]
xmlns:local="clr-namespace:the-namespace-of-my-wpf-stuff"
[...]
<local:SomeView DataContext="{Binding SomeViewModel}" />
<local:OtherView DataContext="{Binding OtherViewModel}" />
[...]
As you can see, a viewmodel can simply be a member (child) of another viewmodel; in this case SomeViewModel and OtherViewModel are children of MainViewModel. Then, in the XAML file of MainView, you can just instantiate each of the child views and specify their DataContext by Binding to the corresponding child viewmodels.