WPF usercontrol mvvm - c#

I have a problem implementing MVVM with a usercontrols.
I have an MVVM based application.
In one of the view (which is a usercontrol) I have a menu on the left and content on the right. The content change depending on the menu.
I tried to implement the MVVM with a usercontrol, but i dont know how.
Here is what i tried but it didn't work :
<UserControl x:Class="PoS.Views.OptionsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PoS.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate x:Name="SettingsTemplate" DataType="{x:Type viewmodels:SettingsViewModel}">
<views:SettingsView DataContext="{Binding}" />
</DataTemplate>
</UserControl.Resources>
<Grid>
</Grid>
</UserControl>

I'll be honest, I think you need to rewind a bit and read a good book on MVVM before continuing. Gary McLean Hall's Pro WPF and Silverlight MVVM is a good place to start.
To answer your question, I'll assume that this user control is set up with its DataContext pointing to your MainViewModel. The content on the right needs a corresponding property in the main view model i.e. something like this:
private ViewModelBase _CurrentPage;
public ViewModelBase CurrentPage
{
get { return this._CurrentPage; }
set
{
if (this._CurrentPage != value)
{
this._CurrentPage = value;
RaisePropertyChanged(() => this.CurrentPage);
}
}
}
You then create a bunch of "pages" or something that inherit ViewModelBase i.e. Page1ViewModel, Page2ViewModel, SettingsViewModel etc. You then create a ContentControl and bind its content to that property:
<ContentControl Content="{Binding CurrentPage}" />
So now if your view model does something like CurrentPage = new SettingsViewModel() then the ContentControl will be populated with whatever you declared as the DataTemplate for that type (i.e. a control of type views:SettingsView). If you assign the property to something else then the SettingsView will be destroyed and replaced by whatever the DataTemplate for the new type is.
In your example above only SettingsViewModel/SettingsView will work, because that's all you've created a DataTemplate for; in order for this to work you need to create a separate DataTemplate for each ViewModel/View pair type you create.

Related

Creating a child Window without an MVVM framework

I'm writing a small application whilst learning MVVM in WPF.
As long as I keep on using one Window, everything is pretty easy.
Now I want to open a new Window with a specific ViewModel.
I have a main ViewModel, which contains a Command that should open a new Window / ViewModel, along with a Parameter.
To do this in an MVVM way, I've created a NavigationService, which I'd like to call like this:
public MainWindowViewModel()
{
DetailsCommand = new DelegateCommand(Details);
}
public void Details()
{
SessionsViewModel sessions = new SessionsViewModel();
_NavigationService.CreateWindow(sessions);
}
I've noticed that it's possible to "bind" Views and ViewModels in XAML, like this:
<Application x:Class="TimeTracker.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TimeTracker"
xmlns:vm="clr-namespace:TimeTracker.ViewModels"
xmlns:vw="clr-namespace:TimeTracker.Views"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type vm:MainWindowViewModel}">
<vw:MainWindow />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SessionsViewModel}">
<vw:Sessions />
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
<Window x:Class="TimeTracker.Views.Sessions"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TimeTracker.Views"
xmlns:vm="clr-namespace:TimeTracker.ViewModels"
mc:Ignorable="d"
Title="Sessions" Height="300" Width="300">
<Window.DataContext>
<vm:SessionsViewModel/>
</Window.DataContext>
<Grid>
<TextBlock Text="Hallo" />
</Grid>
</Window>
The problem I'm having is that I don't know how I can use this ResourceDictionary in my NavigationService, so that I can create a new Window by only using its ViewModel.
class NavigationService
{
public void CreateWindow(IViewModel viewModel)
{
//How do I create a new Window using the ResourceDictionary?
}
}
Make sure you have a ContentControl or ContentPresenter in your new window so that the ViewModel can be presented. Next, make sure that resource dictionary is in scope. Putting it in Application.Resources will make it global and guarantee that WPF can find the DataTemplate.
Also, don't use a Window class as your view in the DataTemplate. Use your sub-window panel (e.g., Grid, StackPanel, etc).
I do this:
<blah:MyChildWindow>
<ContentControl Content={Binding DataContext}/>
</blah:MyChildWindow>
And in Application.Resources:
<DataTemplate DataType={x:Type blah:MyViewModel}>
<blah:MyChildWindow/>
</DataTemplate>
BTW - using DataTemplates the way you are trying to do is an excellent pattern.

How can I bind other components besides ContentControl to a view in Caliburn Micro?

So in Caliburn Micro, I have been using the following method to compose a view inside of another view:
Put a ContentControl inside the composing View.
Create a property on the composing ViewModel, and assign to it the composed ViewModel
Give the ContentControl a x:Name attribute that matches the name of the composed ViewModel property on the composing ViewModel.
like so...
View:
<UserControl x:Class="MyProject.MyComposingView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
<ContentControl x:Name="MyComposedViewModel"/>
</UserControl>
ViewModel:
class ComposingViewModel : PropertyChangedBase
{
private ComposedViewModel _myComposedViewModel;
public ComposedViewModel MyComposedViewModel
{
get { return _myComposedViewModel; }
set
{
_myComposedViewModel= value;
NotifyOfPropertyChange(() => Page);
}
}
public ComposingViewModel(ComposedViewModel myComposedViewModel)
{
MyComposedViewModel = myComposedViewModel;
}
}
Caliburn Micro automagically figures out that because it's a ContentControl it obviously doesn't want to bind to a ViewModel, but rather to its associated View, and so it does something under the hood to bind the ContentControl's Content property to MyComposedView instead of MyComposedViewModel.
But, what if I don't want to use a ContentControl? Like, maybe some reusable custom component of mine that wraps a ContentControl instead? For example:
<UserControl x:Class="MyProject.MyContentWrapper"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d">
<Grid x:Name="PreviewBox" SizeChanged="onSizeChanged">
<Image x:Name="BGImage" Source="{Binding BGImage}"/>
<ContentControl Content="{Binding}"/>
</Grid>
</UserControl>
If I replace the ContentControl with a MyContentWrapper, CaliburnMicro no longer works its magic to supply MyComposedView, and I end up with a TextBlock that says, MyProject.MyComposedViewModel.
How can I get CaliburnMicro to know this is a situation where it should supply the View rather than the ViewModel?
What you want to do is add a convention for your custom control:
Go to the code for ConventionMananger on github.
Search for AddElementConvention<ContentControl>.
Create a new method in your Bootstrapper that runs when your application starts. Add a call to ConventionManager.AddElementConvention<YourControl> similar to the one for ContentControl.
Make sure to put a ContentPropertyAttribute on your control and specify the content property.
Disclaimer: I'm on mobile and can't validate this.

MVVM - Add a Existing User Control in Grid(XAML)

I am developing a Windows 8.1 apps,
and i am following MVVM Pattern
I have a Grid in the Application
<Grid Name="g1">
in which in need to add a existing User Control.
<UserControl
x:Class="CaptureApp.UIComponents.PlayVideo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CaptureApp.UIComponents"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<MediaElement Name="MediaPlay" >
</MediaElement>
</Grid>
</UserControl>
Since View (XAML) is not allowed to know the Control.
What will be the correct way to implement it??
the wordpress blog in the comments uses a datatrigger, which isn't present in windows store apps.
if I understand your question correctly, you're trying to have a view within your grid that is conditionally loaded, so that when there is no data for the user control, it is not rendered in the grid?
you could accomplish this by using a
<ContentControl Content="{Binding PropertyOnViewModel}" ContentTemplateSelector="{StaticResource SomeContentTemplateSelector}" />.
public class SomeContentTemplateSelector : DataTemplateSelector
{
public DataTemplate SomeTemplate {get;set;}
protected override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is null)
return null;
return SomeTemplate;
}
}
and then in a DataTemplate, have your UserControl as a child. This will display nothing when there is no Content bound to the ContentControl, and will otherwise display the supplied DataTemplate. You will need to have a property in the over-arching ViewModel that contains the content for this ContentControl, though, just fyi.
edit: if you're adding multiple items dynamically, then you will want an ObservableCollection<> property on your ViewModel, and use an ItemsControl instead of a ContentControl.

dynamically display different View for different ViewModel but the same attribute

Ok, I'm trying to get to grips with MVVM. I have an application that has multiple options for image capture. Depending on the mode, the image is either loaded from an existing file, or captured from a camera.
I'm writing a page using the MVVM pattern which represents the configuration of the image capture device.
The model consists of two classes which expose the specific (and non common) values for each of the modes which conform to a common interface of IImageSource.
Each of the two model classes have a contextually defined viewmodel:
CameraSourceViewModel
FileSourceViewModel
and two corresponding views.
CameraSourceView
FileSourceView
The model has an attribute which returns IImageSource.
I'm currently using third view, ImageSourceView as the page. I'm handling the loading event which gets the value from the model, then, depending on the type will instantiate the correct viewmodel and the correct view to go with it and then adds that as it's content. However, this seems to be going against the spirit of MVVM in that I've now written some decision code in the code behind
Is there a more elegant/ better way of determining which viewmodel/ view should be instantiated and used?
Actually, you shouldn't need a TemplateSelector, since the two ViewModels will have different types. You can declare DataTemplates in XAML as resources with the model type as key, so that WPF chooses the correct DataTemplate automatically:
Have a main ViewModel, which exposes a ImageSourceViewModel property. This property would either return a CameraSourceViewModel or a FileSourceViewModel, as appropriate.
In your page, the DataContext would be the main ViewModel, and you'd have XAML like this:
Code Example:
<Page x:Class="Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:my="clr-namespace:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="Page1">
<Page.Resources>
<DataTemplate DataType="{x:Type my:CameraSourceViewModel}">
<my:CameraSourceView/>
</DataTemplate>
<DataTemplate DataType="{x:Type my:FileSourceViewModel}">
<my:FileSourceView/>
</DataTemplate>
</Page.Resources>
<Grid>
<ContentControl Content="{Binding ImageSourceViewModel}"/>
</Grid>
</Page>
Here's some idea about what you could do:
Have a ImageSourceViewModel, that is the ViewModel of your ImageSourceView view. It would be the role of this viewModel to get "your value" from the model, and expose it as a public property of type IImageSource.
Then, in your ImageSourceView view, you could use a template selector to change the content of the view, depending on the concrete type of the exposed IImageSource property.
See http://www.codeproject.com/Articles/418250/WPF-Based-Dynamic-DataTemplateSelector
You have two palces where you need decide which type to use in run time:
ViewModel
View
On ViewModel level just use ViewModel factory, so just by an EventType/ValueType instantiates an appropriate ViewModel:
private IImageSourceViewModel ProcessEvent(IEvent someEvent)
{
return viewModelFactory.Create(someEvent.Type)
}
Then on View level just use DataTemplateSelector which accepts via binding already resolved ViewModel instance and then decides which View to use:
MainView XAML:
<ContentControl
Content="{Binding ImageSourceViewModel}"
ContentTemplateSelector =
"{StaticResource ImageSourceViewDataTemplateSelector}">
</ContentControl>
ImageSourceViewDataTemplateSelector:
private sealed class ImageSourceViewDataTemplateSelector: DataTemplateSelector
{
public ImageSourceViewDataTemplateSelector(... dependencies if any...)
{
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
DataTemplate dataTemplate = null;
IImageSourceViewModel instance = item as IImageSourceViewModel;
// move out into the constructor
var dataTemplateFactory = new Dictionary<Type, Func<DataTemplate>>
{
{ typeof(ICameraSourceViewModel), (x) => this.Resources["CameraSourceDataTemplate"] as DataTemplate },
{ typeof(IFileSourceViewModel), (x) => this.Resources["FileSourceViewModel"] as DataTemplate }
};
// TODO: handle not supported type case yourself
return dataTemplateFactory[instance.GetType()]();
}
}

How can I find an item with specific value, in a Collection bound to a Canvas

I've been Google'ing a bit around for this, but without getting any real answer. Probably because my question might be a bit cryptic. Here goes:
Lets say i have an ObservableCollection<SomeModel> containing a bunch of models. I then add the corresponding Views to a Canvas. Specifying this in the resources of the Window, and then bind the Canvas' ItemsSource to the ObservableCollection<SomeModel>. This works fine.
SomeModel is bound to SomeView, this is a UserControl.
Now, when this View gets focus, or when I MouseDown on it, I would like to have it marked as "Selected". Somehow, i would like to have a property in the codebehind to the Window holding my Canvas, where i can always get the selected item.
I've been thinking of having a BindingList instead of the ObservableCollection, and when an IsSelected property on the model changes, then a method will extract the selected item from the list. But this seems to be a bit of a performance killer, as i will be notified on all changes to the items.
How can I accomplish this?
There are multiple ways you could solve this. But probably the simplest it to work with a ListBox and bind to it. ListBox as it has bindable ItemsSource and a SelectedItem property that has the item that is currently selected in it. It also calls the SelectionChanged event when the selection changes if you want to do something in the code behind .cs file.
I would recommend keeping the ObservableCollection in your View Model to keep true to MVVM.
If the style or placement of ListBox does not suit your override the template to something that suits your needs better.
You could look at a behaviour if the above doesn't work for you but best to keep it simple.
UPDATE
This is how you would build the view. Note the ItemsPanel attribute is bound to a defined ItemsPanelTemplate in the UserControl.Resources section which specifies a Canvas to put the items on.
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:local="clr-namespace:SilverlightApplication1"
x:Class="SilverlightApplication1.View1"
d:DesignWidth="640" d:DesignHeight="480">
<UserControl.Resources>
<local:View1Model x:Key="View1ModelDataSource" />
<ItemsPanelTemplate x:Key="ItemsPanelTemplate1">
<Canvas />
</ItemsPanelTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource View1ModelDataSource}}">
<ListBox Margin="80,85,183,54" ItemsPanel="{StaticResource ItemsPanelTemplate1}" ItemsSource="{Binding DataModelCollection}"/>
</Grid>
On the View Model
public class View1Model
{
private ObservableCollection<SomeModel> _DataModelCollection;
public ObservableCollection<SomeModel> DataModelCollection
{
get { return this._DataModelCollection; }
set { this._DataModelCollection = value; }
}
}
It should be noted though that Canvas itself does not have any logic to let the user move controls around on it at runtime.

Categories

Resources