Setting ViewModel Property from Method in Another Project - c#

Beginner here - I'm struggling to wrap my head around accessing viewmodel properties. In my particular case, I have a solution containing two different projects. One contains the UI components, the other the majority of the "work."
My viewmodel in my UI project contains the following property:
private int _createfileprogress { get; set; }
public int CreateFileProgress
{
get { return _createfileprogress ; }
set
{
if (value == _createfileprogress )
return;
_createfileprogress = value;
OnPropertyChanged("CreateFileProgress");
}
}
The purpose of this int property is to fill in the progress of a progress bar. I'd like to reference it in my other project, where a long-running method is executed as such:
public void LongRunningMethod()
{
CreateFileProgress= 0;
// Do some calculations
CreateFileProgress= 10
// More calculations and external calls
CreateFileProgress= 20
// etc.
}
But I can't find the right way to connect these two. I'm sure doing this wrong - would appreciate any guidance. Thanks!

I hope i understand your question.
Your ViewModels and the View are in the same Project und you want to monitor the progress from the Model which is in an another Project?
i think you are searching for Events / Observer Pattern
In MVVM the Model didn't care about the ViewModel and the View.
The Prober Way in MVVM is that the model raise Events which the Viewmodel can subscribe.
General Event Example in .Net
You can create an EventArgs Class like
public class ProgressEventArgs :EventArgs
{
public int CurrentProgress { get; }
public ProgressEventArgs(int progress)
{
CurrentProgress = progress;
}
}
and create the Event in your Model (LongRunningMethod) Class
public event EventHandler<ProgressEventArgs> ProgressChanged;
protected void OnProgressChanged(int progress)
{
ProgressChanged?.Invoke(this, new ProgressEventArgs(progress));
}
So your Method can raise Events
public void LongRunningMethod()
{
OnProgressChanged(0);
// Do some calculations
OnProgressChanged(10);
// More calculations and external calls
OnProgressChanged(20);
// etc.
}
which the ViewModel subscribe
public class ProgressViewModel
{
public ProgressViewModel()
{
var model = new Model();
model.ProgressChanged += (sender, e) => {
//invoke ui thread
Application.Current.Dispatcher.Invoke(
new Action(() =>
{
CreateFileProgress = e.CurrentProgress;
}));
};
}
}

It seems that what might best help you would be to set the DataContext of your UI element (either the Window, the Page, or the ProgressBar itself) to your ViewModel, which will allow your UI to be bound to ViewModel properties. Since your ViewModel seems to implement the INotifyPropertyChanged interface, your OnPropertyChanged() method should automatically keep the UI up-to-date when you use DataBinding.
You'll first want to make a reference to your class library project that contains the "work". This will allow your "UI project" to access the resources (namely, the ViewModel) from the "work project". Do this by right-clicking References under your "UI Project" in the Solution Explorer, choose Add Reference..., find the Projects tab on the left side, then check the box for your "work project". Apply it and your "UI project" can now access your "work project".
Once that is done, you can use either of the following methods to produce the result I believe you're looking for:
Set the Window's DataContext to your ViewModel
(Best for when many elements within your Window will use resources from your ViewModel)
<Window
//Some attributes
//Some more attributes
xmlns:vm="clr-namespace:MyWorkProject.ViewModels;assembly=MyWorkProject">
<Window.DataContext>
<vm:MyViewModel/>
</Window.DataContext>
<Grid>
<ProgressBar Value="{Binding CreateFileProgress}/>
</Grid>
</Window>
OR
Set only the ProgressBar's DataContext using a StaticResources
(Best if you don't need the entire Window's DataContext to be the ViewModel)
<Window
//Some attributes
//Some more attributes
xmlns:vm="clr-namespace:MyWorkProject.ViewModels;assembly=MyWorkProject">
<Window.Resources>
<vm:MyViewModel/>
</Window.Resources>
<Grid>
<ProgressBar Value="{Binding Source={StaticResource MyViewModel}, Path=CreateFileProgress}/>
</Grid>
</Window>

Related

Control the view in MVVM

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.

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.

How does a View know what ViewModel to use in WPF?

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.

How do I let the View know its have to close the Window?

I am using MVVM, from one I know the commnication for Data is View <=> ViewModel <=> Model.
The ViewModel can interact with View by using Two-Way binding.
But now I have a LoginView (which is in a Window), if the login successful will be check in ViewModel.
When it fail, it should tell the Window to close. But...how? The ViewModel is not suppose to know about the View....
Your ViewModel is a representation of your UI state. You could simply have a IsLoginWindowVisible boolean property exposed, your view can then have code (yes, I said it, view can have code!) that shows / hides a windows based on the state of this property.
I think people stress too much about MVVM. As long as your ViewModel can execute without a view present, in order to facilitate testing, you are doing MVVM correctly. There is nothing wrong with having code to support your view.
I would go simple here and use an event to notify the view it should close.
ViewModel:
public event EventHandler LoginFailed;
public void Login()
{
if (fail)
{
if (this.LoginFailed != null)
{
this.LoginFailed(this, EventArgs.Empty);
}
}
}
View:
((MyViewModel)this.DataContext).LoginFailed += (sender, e) =>
{
// Code to close the window, such as window.Close();
};
You should add a specific event to your ViewModel (something like LoginFailed). Then you should link this event to a command that closes the window. See this blog post on how to link the two.
Here's another option. Instead of using an event you could use a delegate:
public class View {
...
myViewModel.OnFail = () => {this.Close();};
...
}
public class MyViewModel {
public Action OnFail {get; set;}
private void Login() {
....
if (failed && OnFail != null)
OnFail();
}
}

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