Control the view in MVVM - c#

I created a project with the MVVM model, and done so with the view-first approach.
I have a TextBox in my XAML code, along with a Button to pass the data from the TextBox:
<!-- View - XAML code -->
<TextBox
MinWidth="30"
Name="TagId"/>
<Button
Command="{Binding AddTagCommand}"
CommandParameter="{Binding Text, ElementName=TagId}"
Content="Add"/>
When I click the button, I want the TextBox cleared.
According to the Prism manual:
In some cases, the code-behind may contain UI logic code that implements visual behavior that is difficult or inefficient to express in Extensible Application Markup Language (XAML), such as complex animations, or when the code needs to directly manipulate visual elements that are part of the view.
Here's the code behind, and the viewmodel.
//View - code behind
public partial class ApplicationStarterView : UserControl
{
public ApplicationStarterView()
{
}
public ApplicationStarterView(ApplicationStarterViewModel viewModel) : this()
{
DataContext = viewModel;
InitializeComponent();
}
}
//View model
public class ApplicationStarterViewModel : BindableBase
{
private readonly IUnityContainer _container;
public ApplicationStarterViewModel(IUnityContainer container)
{
_container = container;
AddTagCommand = new DelegateCommand<object>(AddTag);
}
public ICommand AddTagCommand { get; private set; }
private void AddTag(object input)
{
//Forward stuff
//Clear TextBox
}
}
Can I in any way squeeze in some code to do a TagId.Clear()?

I'd bind the text to another property on the view model.
That way, you can skip the command parameter and the AddTagCommand can directly read the new Text property, do his adding stuff and then clear it, thus updating TagId.
Completely unrelated piece of advice: it is almost never a good idea to inject the IUnityContainer... if you need to create stuff, use factories.

Related

How to share a binding value between multiple views?

I am new to WPF and MVVM (coming in from WinForms and Events), so please bear with me!
I am trying to figure out how to use the same INotifyPropertyChanged value binding between multiple views. I am using MVVM Light. I have ViewModels that inherit from ViewModelBase backing my Views (with no code behind). I'm not sure how to explain the issue, but I think an example will make it clear what I'm after.
I have a main window. It has a standard TabControl. On the Login TabItem I have a custom login control. Below the TabControl, I have a custom status bar control. The desired behavior is that when the user logs in, the status bar is updated with their login status and name, and the other TabItems on the main window become enabled (they should be disabled when not logged in).
So to summarize I have:
MainWindow (view) with MainWindowViewModel
Login (view) with LoginViewModel (in TabControl of MainWindow)
StatusBar (view) with StatusBarViewModel (at bottom of MainWindow)
Here is what my StatusBarViewModel looks like:
public class StatusBarViewModel : ViewModelBase, IStatusBarViewModel
{
private bool _isLoggedIn;
public bool IsLoggedIn
{
get { return _isLoggedIn; }
set { Set(ref _isLoggedIn, value); RaisePropertyChanged(); }
}
// other properties follow
}
I inject (using Ninject) the (singleton) concrete instance of IStatusBarViewModel into the LoginViewModel via constructor injection:
public class LoginViewModel : ViewModelBase
{
private IStatusBarViewModel _statusBar;
public LoginViewModel(IStatusBarViewModel statusBar)
{
_statusBar = statusBar;
}
}
And I do the same for the MainWindowViewModel:
public class MainWindowViewModel : ViewModelBase
{
private IStatusBarViewModel _statusBar;
public bool IsLoggedIn => _statusBar.IsLoggedIn;
public MainWindowViewModel(IStatusBarViewModel statusBar)
{
_statusBar = statusBar;
}
}
Note: I think this is where my problem is... Not sure if MVVM Light interprets this as a bindable property and applies the proper change notifications. If I add a setter (which I don't need here), that won't work because A property or indexer may not be passed as an out or ref parameter. So I'm unclear on what is going on when I do this.
Back on track here, so when the login is successful, I am able to update the IsLoggedIn property from the LoginViewModel like so:
_statusBar.IsLoggedIn = true;
I set up the binding in my MainWindow XAML like so:
<TabItem Header="Event" IsEnabled="{Binding IsLoggedIn}">
<views:Events/>
</TabItem>
The binding works correctly when the view is first loaded, but subsequent changes to the property don't trigger a change in IsEnabled. The StatusBar (view) however does update accordingly.
I had tossed around the idea of injecting references to both the StatusBarViewModel and the MainWindowViewModel in to my LoginViewModel (and then having to set two properties after login), but that made me think that I'm not approaching this correctly because I'm creating dependencies.
So basically the question is:
Is my approach correct, per the MVVM pattern?
Am I on the right track and just need to modify the code a bit?
If not, what is the (or a) standard pattern to handle this scenario?
Your guess is correct. The problem is here:
public bool IsLoggedIn => _statusBar.IsLoggedIn;
... because it's not going to generate the change notification. What you could do is just expose the IStatusBarViewModel via a public property and then bind to its own IsLoggedIn property directly.
In the ViewModel:
public class MainWindowViewModel : ViewModelBase
{
private IStatusBarViewModel _statusBar;
public IStatusBarViewModel StatusBar => _statusBar;
public MainWindowViewModel(IStatusBarViewModel statusBar)
{
_statusBar = statusBar;
}
}
And in the View:
<TabItem Header="Event" IsEnabled="{Binding StatusBar.IsLoggedIn}">
<views:Events/>
</TabItem>

Prism: View First with multiple ViewModels

I'm using Prism with Unity IOC-Container in a WPF-Project. For all my other Views I'm using only one ViewModel per View. Because this View should be a Mask for both Input and Output of Data, I'd like to use two ViewModels.
For the current navigation to the View i use this Code:
_regionManager.RequestNavigate(RegionNames.ContentRegionName, typeof(Events).ToString());`
The Code Behind of my View:
public partial class Events : UserControl
{
public Events(EventsViewModel viewModel)
{
InitializeComponent();
}
}
One of the ViewModels:
public class EventsViewModel : BindableBase
{
public EventsViewModel()
{
// Some Code
}
// Some other Code
}
I heard about ViewModel Discovery, where you give the Constructor of the View an Interface instead of an actual ViewModel. But i could only find exacly this much information.
// Example of such a Method
public Events(IViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel
}
public Interface IViewModel
{
}
My question is now: How do I navigate to the View and tell it wich ViewModel it should get as DataContext? I'm relatively new to programming and the MVVM-Pattern and english is not my native language so maybe I missed some Information. I would be glad if someone had an answer for this. Thanks in advance.
Edit: Workaround
I came up with a workaround wich works for me. I used the method SetDefaultViewTypeToViewModelTypeResolver() from the ViewModelLocationProvider and customized it.
// Bootstrapper.cs
protected override void InitializeShell()
{
var window = (MainWindow)this.Shell;
Application.Current.MainWindow = window;
// Calling the method
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(ResolveViewModel);
var regionManager = Container.Resolve<IRegionManager>();
window._regionManager = regionManager;
globalRegionManager = regionManager;
regionManager.RegisterViewWithRegion(RegionNames.ContentRegionName, typeof(StartScreen));
regionManager.RegisterViewWithRegion(RegionNames.ContentRegionName, typeof(Stock));
window.Show();
}
// Property for handing over the desired ViewModel
public static Type DynamicViewModel { private get; set; }
private Type ResolveViewModel (Type viewType)
{
string _viewModel = null;
var name = viewType.FullName.Replace(".Views.", ".ViewModels.");
if (DynamicViewModel != null)
_viewModel = DynamicViewModel.ToString();
else
_viewModel = $"{name}ViewModel";
var fullName = IntrospectionExtensions.GetTypeInfo(viewType).Assembly.FullName;
var typeString = string.Format(CultureInfo.InvariantCulture, $"{_viewModel}, {fullName}");
DynamicViewModel = null;
return Type.GetType(typeString);
}
Then when I want to navigate, I hand over the ViewModel beforehand.
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
Bootstrapper.DynamicViewModel = typeof(EventsViewModel);
_regionManager.RequestNavigate(RegionNames.ContentRegionName, typeof(Events).ToString());
}
A little tricky but it seems to work without any Exceptions.
If there is a cleaner way I'm alway happy to here it. :)
Here are some techniques for getting the view model for a view (view first).
View discovery in views code behind constructor
public EventsView(EventsViewModel view_model)
{
InitializeComponent();
DataContext = view_model;
}
Explicitly newing up the view model in code behind constructor
public EventsView()
{
InitializeComponent();
DataContext = new EventsViewModel();
}
View model locator in the XAML for the view
<UserControl x:Class="EventsModule.Views.EventsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<TextBlock Text="{Binding EventName}"></TextBlock>
</Grid>
</UserControl>
As long as you register your view with the region manager using any one of the techniques below, any one of the above will work. Then navigate like you are doing in your question.
RegionManager.RegisterViewWithRegion(RegionNames.ContentRegionName, typeof(EventsView));
UnityContainer.RegisterType(typeof(object), typeof(EventsView), typeof(EventsView).FullName);
UnityContainer.RegisterTypeForNavigation<EventsView>(typeof(EventsView).FullName);
The first will activate the view in the region and is usually seen in the module Initialize method. Those last 2 are for registering a view for later navigation. The last one requires the Prism.Unity namespace.
I don't believe that a view can have more the one view model since the view object only has one DataContext property on it. You may have to extend one view model to include everything you need. Someone may come along and prove me wrong on this. I have seen where a view model is shared with more than one view, but never a view having more than one view model.

Databinding with unnamed textboxes and listbox

I have been taught lately when using WPF and databinding it is good practice to not name any of the fields but only to associate them with the properties in the other classes. My problem right now is how do I add the data from 3 textboxes (the user enters), save the binded information to the model which then posts the account information into the listbox on the side. I need to add the data to my model. My code from main.xaml is below:
<ListBox ItemsSource="{Binding Path=Files}" SelectedItem="{BindingPath=CurrentItem}" />
<TextBox Text="{Binding Path=bankaccount}"/>
<TextBox Text="{Binding Path=accountnumber}"/>
<TextBox Text="{Binding Path=accounttype}"/>
<Button Content="Save Data To Listbox" Click="Save_Click"/>
Now I will show my FileModel class which holds all of my properties which will be from the textboxes
private short _BankAccount;
private long _AccountNumber;
private char _AccountType;
public short bankaccount{ get { return _BankAccount;} set {_BankAccount= value; Notify("bankaccount"); } }
public long accountnumber{ get { return _AccountNumber;} set {_AccountNumber= value; Notify("accountnumber"); } }
public char accounttype{ get { return _AccountType;} set{_AccountType= value; Notify("accounttype"); } }
I use a class called ProgramModel As my middle point between the Mainpage and my FileModel page and here is that code:
public class ProgramModel : INotifyPropertyChanged
{
public ObservableCollection<FileModel> Files { get; set; }
private FileModel _currentItem;
public FileModel CurrentItem { get { return _currentItem; } set { _currentItem = value; Notify("CurrentItem"); } }
public ProgramModel()
{
Files = new ObservableCollection<FileModel>();
}
And to finish it off I have my mainpage:
internal partial class MainWindow
{
public ProgramModel Model { get; set; }
private ViewSettings _viewSettings = new ViewSettings();
public MainWindow()
{
InitializeComponent();
this.DataContext = Model = new ProgramModel();
}
private void Save_Click(object sender, RoutedEventArgs e)
{
FileModel filemodel = new FileModel();
Model.Files.Add(new FileModel( filemodel.bankaccount, filemodel.accountnumber, filemodel.accounttype));
}
I feel like I am adding to the Files Collection incorrectly from the save button event. If you guys can help me out that would be great! All 3 textboxes and the listbox are on the Main page. Let me know if you have any questions. Also, this is a learning experience so let me know if I posted too much code or not enough. Thanks!
You read the values from a new FileModel instance instead of from what is bound to the view. Code should be this:
Model.Files.Add(new FileModel
(
Model.CurrentItem.bankaccount,
Model.CurrentItem.accountnumber,
Model.CurrentItem.accounttype
));
Make sure CurrentItem is actually initialized with an instance, don't see that in your code. Also, you could use a command here and have all the relevant logic in your bound view model without the need for the event.
Also, right now you bind the current item to the selected item in the ListBox, this will modify an existing instance instead. Not sure if this is intended. If you want those fields to be for input of new instances don't bind the ListBox to it.
I'm not going to answer your question directly because implementing proper data binding will take a bit of code to do so.
Using proper data binding, it is possible to have almost no code behind on your view.cs! (Specially if you start using frameworks)
Please take a look on A Simple MVVM Example for you to follow good practice.
By following this example, you will see that you can also use data binding on buttons and other controls.
Your View Model which is ProgramModel : INotifyPropertyChanged should handle all the work (data processing).
Your model should not handle the UI update notifications thus,
public short bankaccount{ get { return _BankAccount;} set {_BankAccount= value; Notify("bankaccount"); } }
will be moved to the ProgramModel (View Model).
Save_Click method will also be converted into an ICommand and be binded to the button in view like <Button Content="Save Data To Listbox" Command="{Binding SaveExec}"/>
The point is, if you are studying data binding, you should implement it right. Hope you understand...
In the end, it is possible for your Main.cs to only be..
internal partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ProgramModel();
}
}
Just a small change and it should work . Change Your bindings as shown below for the TextBoxes.
<TextBox Text="{Binding Path=CurrentItem.bankaccount}"/>
<TextBox Text="{Binding Path=CurrentItem.accountnumber}"/>
<TextBox Text="{Binding Path=CurrentItem.accounttype}"/>

How to re-wire View data context to XAML from C#?

I've set my View's data context to it's associated ViewModel in the View's code behind. But after reading a question on the MVVM pattern, it's suggested to move this glue code to the View's XAML mark up.
Googling has shown me examples of setting the context below in XAML, by setting a namespace to the VM and setting data context.
Although in my case, the MainViewModel takes a parameter of a CustomerRepository instance, which I'm not sure how to set up in XAML, base on the previous example.
Does anyone know how to move the data context to the View's XAML?
This is how I've currently set the View's code behind in C#:
public partial class MainView : Window
{
private MainViewModel ViewModel { get; set; }
public MainView()
{
InitializeComponent();
ViewModel = new MainViewModel(CustomerRepository.Instance);
this.DataContext = ViewModel;
}
}
You can instantiate your view model in your xaml like this:
<Window x:Class="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test"
xmlns:viewModel="clr-namespace:ViewModels">
<Window.DataContext>
<viewModel:ExampleViewModel/>
</Window.DataContext>
</Window>
You explained that your view model constructor has a parameter for your repository. Do you need this constructor for unit testing purposes?
Usually you can just new up your repository instance in a parameterless constructor of the view model.
public class MainViewModel : ObservableObject, INotifyPropertyChanged
{
private static CustomerRepository _customerRepository;
// existing constructor
public MainViewModel(CustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
// new parameterless constructor
public MainViewModel(CustomerRepository customerRepository)
{
if (DesignerProperties.GetIsInDesignMode(this))
{
_customerRepository = new CustomerRepository();
}
}
}
Check if is in design mode
There is one more thing you need to think of when creating a view model in xaml: The view models constructor is called at design time when you open the view.
So you need to wrap any code in the constructor which does not make any sense at design time into a "only when not in design time" condition.
If you do not do this, your view will open with an error in the designer.
This is explained in this response: Check if in design mode

Prism MVVM - Add child View to parent View without using Regions and Injection, just XAML

I'm fairly new to the Silverlight and the MVVM / Prism pattern so this may be a stupid question.
I have a View which has custom controls within it. These custom controls are actually Views too and have ViewModels to drive them.
Currently to add these 'child' Views to the View I'm using (see Fig.1) and then in the ViewModel I have an Initialise() method which resolves the child View and injects it (see Fig.2).
Fig.1
<UserControl
x:Class="ProjectA.Module.Screens.Views.PlatformScreenView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Regions="clr-namespace:Microsoft.Practices.Composite.Presentation.Regions;assembly=Microsoft.Practices.Composite.Presentation"
>
<Grid x:Name="LayoutRoot">
<ContentControl
Regions:RegionManager.RegionName="FeaturesSelectionControl"
/>
</Grid>
Fig.2
public void Initialise()
{
IRegion featuresRegion = _regionManager.Regions["FeaturesSelectionControl"];
featuresRegion.Add(_container.Resolve<IFeatureSelectionControlViewModel>().View);
}
My question is do I have to do this for every control I want to add? I understand why it works this way but it seems like quite a bit of code and also I need to keep track of all the region names and ensure I don't have any clashes etc. Is there a simpler way of doing this without regions and just in XAML?
I've seen a snippet of XAML on StackOverflow here but not sure how it works and if it's what I want -
<ContentControl Content="{Binding SmartFormViewModel}"/>
Any help is much appreciated.
James
Edit after clarification:
It appears you don't want to use RegionManager at all, which is fine. What I would suggest then is this:
Create an interface for your Modules to use to register view creation methods:
public interface IViewRegistry
{
void RegisterMainView(Func<object> ViewCreationDelegate);
}
Your modules will use this like this:
public MyModule : IModule
{
IViewRegistry _registry;
public MyModule(IViewRegistry registry)
{
_registry = registry;
}
public void Initialize()
{
_registry.RegisterMainView(() =>
{
var vm = new MyViewModel();
var view = new MyView();
var view.DataContext = vm;
return view;
});
}
}
Then in your shell, you first implement the view registry (this is a simplification... you'd probably want something more robust)
public ViewRegistry : IViewRegistry
{
public static List<Func<object>> ViewFactories
{
get { return _viewFactories; }
}
static List<Func<object>> _viewFactories = new List<Func<object>>();
public void RegisterMainView(Func<object> viewFactory)
{
_viewFactories.Add(viewFactory);
}
}
And lastly, here's how your shell would show that stuff. Here's its ViewModel first:
public ShellViewModel : ViewModel
{
public ObservableCollection<object> MainViews
{
...
}
public ShellViewModel()
{
MainViews = new ObservableCollection<object>(ViewRegistry.Select(factory => factory()));
}
}
And here's your View (look ma, no RegionManager!):
<UserControl ...>
<Grid>
<ItemsControl ItemsSource="{Binding MainViews}" />
</Grid>
</UserControl>
The region manager sort of attempts to give you everything I've written here, plus a lot of extensibility points, but if you don't like RegionManager or you find it doesn't fit your needs for some reason, this is how you would do this in Silverlight without it.
Further Edits:
After some more commentary from the OP, I think I understand that the OP just wants to show a view within another view without having to use RegionManager. It appears the OP is using RegionManager to show every view on the screen, which is probably overkill.
The scenario I was given included an Address View and associated ViewModel being used from a different parent control. This is what I do (whether right or wrong):
<UserControl x:Class="Views.MyParentView" ...>
<StackPanel>
<TextBlock>Blah blah blah some other stuff... blah blah</TextBlock>
<myViews:AddressView DataContext="{Binding AddressVM}" />
</StackPanel>
</UserControl>
And here's the parent view's viewModel:
public ParentViewModel : ViewModel
{
public AddressViewModel AddressVM
{
...
}
public ParentViewModel()
{
AddressVM = new AddressViewModel();
}
}
That's it. This prevents you from having to work too hard to show these views.
RegionManager is really appropriate for decoupling the parent container view from the subview, but if both of these live in the same place (same module / assembly) there is no reason to use RegionManager to show these views.

Categories