WPF: right way to do a Tabcontrol with MVVM pattern - wpf

First of all, I'm newbie in WPF and specially in MVVM. I have a window with diferent tabs and a very large ViewModel with the business logic of the content of every tab. I know it is not right, so now I'm trying to do it more elegant:
As I see googling, an idea is to do a collection of a "base" viewmodel from wich inherit the sub-viewmodels of every tab, and a collection on this "base" viewmodel in the viewmodel of the window.
TabBaseViewModel
Tab1ViewModel inherits TabBaseViewModel
Tab2ViewModel inherits TabBaseViewModel
MainWindow ViewModel --> Collection of TabBaseViewModel
The contents the tabs do not have anything in common along each other.
How I have to proceed?

You should consider using an MVVM framework if you're using MVVM. With Caliburn.Micro for example, you can define your main view as:
<TabControl x:Name="Items">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
Where the data context is a Conductor type that has a collection. The Items property will expose a collection of your view models:
public class MainViewModel : Conductor<IScreen>.Collection.OneActive
{
private OneOfMyViewModels oneOfMyViewModels;
private AnotherViewModel anotherViewModel;
protected override void OnInitialise()
{
// Better to use constructor injection here
this.oneOfMyViewModels = new OneOfMyViewModels();
this.anotherViewModel = new AnotherViewModel();
this.Items.Add(this.oneOfMyViewModels);
this.Items.Add(this.anotherViewModel);
}
protected override void OnActivate()
{
base.OnActivate();
this.ActivateItem(this.oneOfMyViewModels);
}
}
public class OneOfMyViewModels : Screen
{
public OneOfMyViewModels()
{
this.DisplayName = "My First Screen";
}
}

I posted an answer to a different question which shows how to do exactly this: How to Get a Reference to a ViewModel
It's a very simple example, but hopefully should get you started along the right track.

Related

What is the best way to add new field into Model collection in ViewModel in MVVM app?

I'm writing WPF application with MVVM structure using MVVM Light.
I have class Foo in the Model:
class Foo: ObservableObject
{
private string _propA = String.Empty;
public string PropA
{
get => _propA ;
set
{
if (_propA == value)
{
return;
}
_propA = value;
RaisePropertyChanged("PropA");
}
}
// same for property PropB, PropC, PropD, etc.
}
And I have some collection of Foo objects in the Model:
class FooCollection: ObservableObject
{
private ObservableCollection<Foo> _items = null;
public IEnumerable<Foo> Items
{
get { ... }
set { ... }
}
public string Name { get; set; }
// ...
// and other methods, properties and fields
}
Now I have a ViewModel where this list is populated via some injected provider:
class MainWindowModel: ViewModelBase
{
private FooCollection _fooList;
public FooList
{
get => _fooList;
set
{
_fooList = value;
RaisePropertyChangedEvent(FooList);
}
}
public MainWindowModel(IFooListProvider provider)
{
FooList = provider.GetFooList();
}
}
And the View, with MainWindowModel as data context:
<TextBlock Text={Binding FooList.Name} />
<ItemsControl ItemsSource="{Binding FooList.Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text={Binding PropA} />
<Button Content={Binding PropB} />
<!-- other controls with bindings -->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Everything works fine, I can delete and add new items, edit them and etc. All changes in View reflects automatically in ViewModel and Model via bindings and observable objects, and vice versa.
But now I need to add ToggleButton to data template of ItemsControl, which controls visibility of particular item in other part of window. I need IsChecked value in ViewModel, because control in other part of window is Windows Forms control and I can't bind IsChecked directly without ViewModel.
But I don't want to add new property (Visibility, for example) in model classes (Foo, FooCollection), because it is just an interface thing and it doesn't need to be saved or passed somewhere outside ViewModel.
So my question: what is the best way to add new property to Model collection in ViewModel?
I could create new collection of wrappers in ViewModel (some sort of class Wrapper { Foo item, bool Visibility }) and bind it to ItemsControl. But in this case I have to control adding, removing and editing manually and transfer all changes from List<Wrapper> to FooList.Items, so I don't like this solution. Is there any more simple way to achieve this?
Edition to clarify the question. Now I have:
<ItemsControl ItemsSource="{Binding FooList.Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text={Binding PropA} />
<Button Content={Binding PropB} />
<ToggleButton IsChecked={Binding ????????????} />
<!-- other controls with bindings -->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have no field in class to bind IsChecked and I don't want to add it to class, because it's only interface thing and not data model field. How can I, for example, create another collection of bools and bind it to this ItemControl alongside with FooList.Items?
The best place to add the property is of course in the Foo class.
Creating another collection of some other type, add an object per Foo object in the current collection to this one, and then bind to some property of this new object seems like a really bad solution compared to simply adding a property to your current class.
Foo is not an "interface thing", or at least it shouldn't be. It is view model that is supposed to contain properties that the view binds to. There is nothing wrong with adding an IsChecked property to it. This certainly sounds like the best solution in your case.
I'm not sure if I understand why you would need to add a property in the model.
Can't you just use the command property or add an EventTrigger to your toggle button?
(See Sega and Arseny answer for both examples Executing a command on Checkbox.Checked or Unchecked )
This way, when you check the toggleButton, there is a method in your viewModel which enable or disable the visibility property of your Winform control.
To change the visibility of your control from a command in your viewModel, you could use the messenger functionnality of MVVM LIGHT
MVVM Light Messenger - Sending and Registering Objects
The ViewModel sends a message to you're Windows Forms and this one handles the visibility of your control.

Removing UserControl code behind for use with MVVM

I am trying to create a user control using MVVM.
Basically I am trying to wrap a combobox that will pull data from a respository. This will allow me to use the same combobox in many different views in my application. There will be many of the wrapped comboboxes throughout the application.
I was easily able to create this control using a DependencyProperty and code-behind. I am now trying to convert this to MVVM and am having trouble figuring out how to get the value back to /from the ViewModel that in bound to the View where my combobox is located.
Any ideas or suggestions would be greatly appreciated at this point.
Thanks,
Eric
It is perfectly acceptable to use a UserControl that has code behind in it when using MVVM. If you really want to move the functionality out of the UserControl, then move it to whichever parent view models will require it. If you don't want to have the same code repeated in several places, you could encapsulate it in a class and add an instance of that class as a property to each of the relevant view models.
if you have a viewmodel that will pull data from a respository - you can use the same viewmodel in many different viewmodels in your application :)
and if you use a datatemplate your views know how to render this viewmodel
<DataTemplate DataType="{x:Type local:MyPullDataViewmodel}">
<view:MyCoolPullDataShowComboboxUserControl />
</DataTemplate>
It's pretty easy.
Let's say you have:
MyUserControlView.xaml
MYUserControlViewModel.cs
MyMainWindowView.xaml - For your MainWindow view (the one containing the UserControl)
MyMainWindowViewModel.cs - Your MainWindow ViewModel.
And you want to bind List<string> MyListToBind
And leave the code-behind completely empty.
MyUserControlViewModel.cs
public class MyUserControlViewModel
{
private List<string> _MyListToBind;
public List<string> MyListToBind { get; set;}
}
MyMainWindowViewModel.cs
public class MyUserControlViewModel
{
private MyUserControlViewModel _MyControlViewModel;
public MyUserControlViewModel MyControlViewModel { get; set;}
}
MyMainWindowView.xaml
<Window ...
xmlns:my="clr-namespace:NamespaceContainingYourUserControlView>
<my:MyUserControlView DataContext = "{Binding Path= MyControlViewModel}"/>
</Window>
MyUserControlView.xaml
<UserControl ...>
<DataGrid ItemsSource = "{Binding Path= MyListToBind}" .../>
...
</DataGrid>
</UserControl>
This doesn't support ViewModel updating View. To do that You have to use either DependencyProperties as you did instead of normal variables (as i did) or use INotifyPropertyChanged(google it, you'll get tons of examples) and OnPropertyChanged event.
You might read up on DataTemplates they are really useful in data binding.
You can find this usefeul:
http://www.youtube.com/watch?v=BClf7GZR0DQ
I sure as hell did !
Good luck.

WPF VB.NET - Reference Controls in MainWindow from a Module

I want to use modules in my first WPF application, as I'm used to using them in WinForm applications I've created before. So I have this button, with a textblock inside.
MainWindow.xaml:
<Button x:Name="Connection_Button" Width="200" Height="30" Margin="5,5,5,5">
<TextBlock FontSize="14" MouseDown="TextBlock_MouseDown">CONNECT</TextBlock>
</Button>
Before in WinForms I could easily reference a button and it's text property by adding MainForm. before the control, but how can I do this in WPF through modules, similar to below? How do I even declare the controls at the top of my module code? And elements inside a control such as TextBlock?
Module_Connection.vb in my old WinForm way:
Private Sub Connect()
If MainForm.Connection_Button.Text = "Connect" Then
' Code
You don't usually do this in WPF.
To base your application logic in the state of UI elements is not really a good idea.
Why? because UI is Not Data and therefore you should base your application logic on the state of data items instead.
I suggest you read this and this
To try to use WPF the same way you use winforms is a straight path to miserable failure and suffering.
You must embrace MVVM and understand DataBinding and how the UI must always be separate from Application Logic and Data.
Edit: Adding a Code Sample (in C#)
ViewModel:
public class MainViewModel
{
public string ButtonContent {get;set;} //TODO: Add Property Change Notification
public MainViewModel()
{
ButtonContent = "SomeButtonContent";
}
}
View:
<Button Width="200" Height="30" Margin="5,5,5,5">
<TextBlock FontSize="14" Text="{Binding ButtonContent}"/>
</Button>
Code Behind:
public class MainWindow: Window
{
public MainWindow()
{
InitializeComponent();
SomeModule.Instance = new MainViewModel();
DataContext = SomeModule.Instance;
}
}
Module (static class in C#):
public static class SomeModule
{
public static MainViewModel Instance {get;set;}
public void DoSomething()
{
if (Instance.ButtonContent == "SomeButtonContent")
//...etc
}
}
This is what I mean by separating the UI from the data. You place your strings in a ViewModel, not in the View, then you evaluate the value of those ViewModel properties to determine what to do.
Still, basing your application logic on the value of a string seems like a weak solution. What do you really need to do? There are much better approaches such as using an Enum for this.
Edit2:
Having a ViewModel to Bind to does not "complicate" things. It actually simplifies them a LOT.
How? because you're now doing this with simple controls, but then you'll want to do the same thing with UI elements inside a ControlTemplate or even worse a DataTemplate and that's when real problems arise.
Therefore, you must get used to "the WPF Way" before you deal with more complex UI scenarios.
Non-optimal approach:
public class MainWindow: Window
{
public string ButtonContent
{
get
{
return this.txtButtonContent.Text;
}
set
{
this.txtButtonContent.Text = value;
}
}
}
<Button Width="200" Height="30" Margin="5,5,5,5">
<TextBlock x:Name="txtButtonContent" FontSize="14" Text="Connect"/>
</Button>
You must understand that the Button class doesn't have a Text property in WPF. In contrast to most ancient framweworks, WPF has a Content Model where literally anything can contain anything with little to no restrictions. Putting a Text property to the Button would be to introduce the limitation for the Button to only contain Text, which is not the case in WPF.
Therefore, what you actually want to do here is to modify the Text property of the TextBlock (which happens to be inside the Button, but could actually be anywhere in the Visual Tree).
That's why I mentioned the fact that you actually NEED a ViewModel to hold your data, because there is no (easy) way to access the UI elements located, for example within a DataTemplate in order to manipulate them. Your UI must always be a reflection of the state of your application's Data, which is stored in Model or ViewModel classes, not the UI.
This should get you in the ballpark:
Module:
Public Sub Connect(RyRef txtBox as TextBox)
If txtBox.Text = "...
note Public vs. Private
Call:
Call .Connect(MyTextBox1)
Call and are optional
I'll answer your question in VB.NET and not provide C# samples of alternative ways of doing it which if you're not used too can be an uphill struggle.
Declare this in your module, access the controls through rootWindow.
Private rootWindow As MainWindow = TryCast(Application.Current.MainWindow, MainWindow)
Another approach is :
Dim mw as MainWindow = Application.Current.Windows.OfType(Of MainWindow).First
assuming you have a MainWindow and only have one MainWindow.

How do you pass a ViewModel to the constructor of a UserControl that is shown in a ContentPresenter?

I have several UserControls that should display the same data. Each UserControl has a different layout of the data that is to be presented. The ContentPresenter can bind to any one of the UserControls by using a DataTemplate in my Resources and by binding the Content to a StyleViewModel. Each UserControl is associated with a ViewModel as defined in the DataType of the DataTemplate. The ViewModels associated with any given UserControl all inherit from the StyleViewModel. The UserControls should get their data from a SettingsViewModel. The UserControls appear in the main Window.
The problem is that I can't figure out how to make the data from the SettingsViewModel accessible to the UserControls.
Is it possible to pass a reference to a SettingsViewModel to the constructor of one of these UserControls that are displayed using a ContentPresenter?
Is there another way to easily switch between different views of the data (i.e. my UserControls) without using a ContentPresenter? If so, how would I make the data accessible to the UserControls?
The following is code from my SingleLineViewModel.cs:
public class SingleLineViewModel : StyleViewModel
{
public SingleLineViewModel() { }
}
The other ViewModels are similar. They are essentially empty classes that inherit from StyleViewModel, so that I can bind to a Style property which is of type StyleViewModel in my SettingsViewModel. The StyleViewModel is also an essentially empty class that inherits from ViewModelBase.
The following is code from my Resources.xaml:
<ResourceDictionary <!--other code here-->
xmlns:vm="clr-namespace:MyProject.ViewModel"
<!--other code here-->
<DataTemplate DataType="{x:Type vm:SingleLineViewModel}">
<vw:ucSingleLine/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SeparateLinesViewModel}">
<vw:ucSeparateLines/>
</DataTemplate>
<!--other code here-->
</ResourceDictionary>
The following is code from SettingsViewModel.cs:
public class SettingsViewModel : ViewModelBase
{
// other code here
private StyleViewModel _style;
public StyleViewModel Style
{
get { return _style; }
set
{
if (value != _style && value != null)
{
_style = value;
OnPropertyChanged("Style");
}
}
}
// other code here
public SettingsViewModel()
{
_style = new SingleLineViewModel();
}
// other code here
}
The following is code from my MainView.xaml:
<ContentPresenter Name="MainContent" Content="{Binding SettingsVM.Style}"/>
You might find that you are trying to do much at once. Consider how you might test this scenario? Or how would you walk the data in a Debugger to check its state? Good practice recommends that your data is separate from your UI elements. An MVVM pattern such as you are trying to use normally provides the view models to help transition the data from it's simple data into forms that the UI can use.
With that in mind, I would recommend that you try do develop a ViewModel tier that presents all the data without the UI holding it together, i.e. instead of trying to inject the additional SettingsViewModel into your controls you should make your viewmodels hold everything they need.
It looks like you are off to a good start, your SettingsViewModel lets you get hold of a Style, but your style doesn't seem to have any data. So why not pass it in the constructor.
public SettingsViewModel()
{
_style = new SingleLineViewModel(WhatINeedForStyle);
}

Binding a ContentControl to a deep path in WPF

The application I'm currently writing is using MVVM with the ViewModel-first pattern. I have XAML similar to the following:
<ContentControl Content="{Binding FooViewModel.BarViewModel.View, Mode=OneWay}"/>
Every VM is a DependencyObject. Every property is a DependencyProperty. Depending upon the state of the application, the value of the BarViewModel property of the FooViewModel can change, thus changing the value of the View property. Unfortunately when this happens, the new view is not displayed, and the old one remains.
This is extremely frustrating. I thought that if any part of a path expression changed, the binding would update, but that doesn't appear to be the case. When I've used shallower path expressions, such as FooViewModel.View and I've changed the value of the FooViewModel property, that has updated the ContentControl to which it's bound, but not in this case.
If your solution is that I abandon ViewModel-first, that is not an option, though I appreciate your advice. I must get this working as is.
CLARIFICATION
This is a question about data binding, and not about MVVM or how to implement it. You can safely ignore the MVVM aspects of this if it helps you to think about the problem, or if you have a different idea about how MVVM should be implemented. This is a large, existing project in which the MVVM design pattern cannot be changed. (It is far too late for that.)
So, with that said, the correct question to be answering is the following:
Given a binding path expression in which every element is a DependencyProperty and the final property is a view bound to a ContentControl, why does a change in a property in the middle of the path not cause the binding to update?
Although I would expect this to work, there are several problems with your approach.
Firstly, your view models should not use DependencyObject or DependencyProperty, this ties them in to WPF. They should instead implement INotifyPropertyChanged. This makes your view models reusable in other presentation technologies such as Silverlight.
Secondly, your view models shouldn't have references to your views, so you shouldn't require a View property on your view models.
I would seriously consider using an MVVM framework for view composition - Caliburn.Micro, for example, makes view model first development extremely straightforward, and already provides a view model base class which implements INotifyPropertyChanged, and a mechanism for building view compositions with conventions.
I.e. you can have a conductor view model which has an ActiveItem property, and you simply place a ContentControl on your view with the same name as the property:
<ContentControl x:Name="ActiveItem" />
You can use the ActivateItem() method to change the current active item.
Caliburn.Micro also has a host of other features, such as being able to place a Button control with x:Name="Save" on your view, and your Save method on your view model will automatically be invoked when the button is clicked.
Every VM is a DependencyObject. Every property is a
DependencyProperty.
why? a viewmodel should be a simple class with INotifyPropertyChanged and the Properties should be simple properties.
and if you want your different viewmodel be rendered in a different way - you should use DataTemplate.
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyViewModelA}>
<MyViewA/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:MyViewModelB}>
<MyViewB/>
</DataTemplate>
</Windows.Resources>
<Grid>
<ContentControl Content="{Binding MyActualVM}"/>
</Grid>
</Window>
EDIT: btw you always bind to the last Property: FooViewModel.BarViewModel.View --> so the INotifyPropertyChanged (if raised) just work for the .View
EDIT2: another approach could be to get the BindingExpression of your content control and call.
System.Windows.Data.BindingExpression expr = //get it from your contentcontrol
expr.UpdateTarget();
EDIT3: and a simple mvvm way - just use INotifyPropertyChanged
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.MyFooVM = new FooVM();
this.MyFooVM.MyBarVM = new BarVM(){View = "erster"};
this.DataContext = this;
}
public FooVM MyFooVM { get; set; }
private void Button_Click(object sender, RoutedEventArgs e)
{
this.MyFooVM.MyBarVM = new BarVM(){View = "zweiter"};
}
}
public class INPC : INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropChanged(string property)
{
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs(property));
}
#endregion
}
public class FooVM:INPC
{
private BarVM _myBarVm;
public BarVM MyBarVM
{
get { return _myBarVm; }
set { _myBarVm = value;OnPropChanged("MyBarVM"); }
}
}
public class BarVM : INPC
{
private string _view;
public string View
{
get { return _view; }
set { _view = value;OnPropChanged("View"); }
}
}

Resources