I'm building a WPF application and using caliburn.micro for MVVM.
I'm having dozens of views (UserControls).
The views has a header body and footer.
As shown in below image, header contains two buttons for crud operation, and the footer contains a status bar.
The header and footer parts will be same for all views, but the body contents will be different for view to view.
Currently I'm having repeated code for header and body for each view, and now I'm trying to eliminate repeating code.
To achieve this I'm thinking to make a common base view, to share with all other views.
Current Implementation
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Grid Name="Header/>"
<Grid Name="Body/>"
<Grid Name="Footer/>"
</StackPanel>
</UserControl>
Trying to acheive something like
Base View
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Grid Name="Header/>"
<!-- {Placeholder for child view} -->
<Grid Name="Footer/>"
</StackPanel>
</UserControl>
ChildView
<Grid Name="Body"/>
May be my approach is wrong (I'm bit new to WPF).
My goal is to eliminate the repeating code, by inheriting some controls into the view.
How can I combine base view with child view?
Could anyone advice me to achieve my requirement?
Providing some code example will be highly appreciated.
You could for example define the common header and the footer in the parent window, or in two separate user controls that you create in the XAML markup of the parent window, and then inject the child views into the same window using a ContentControl, e.g.:
<Window x:Class="WpfApplication1.MainWindow"
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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="300">
<StackPanel>
<local:HeaderUserControl />
<ContentControl Content="{Binding CurrentViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:ViewModelA}">
<local:ViewA />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelB}">
<local:ViewB />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
<local:FooterUserControl />
</StackPanel>
</Window>
The child views doesn't know anything about the header and the footer.
You can nest your UserControls just like you can nest any other element so you could do something like this
<UserControl x:Name="HeaderControl">
<UserControl x:Name="ChildControl"/>
</UserControl>
Then you'd use a DependencyProperty in your header control to associate your child control with it something like this
public UserControl ChildControl
{
get { return (UserControl)GetValue(ChildControlProperty); }
set { SetValue(ChildControlProperty, value); }
}
public static readonly DependencyProperty ChildControlProperty =
DependencyProperty.Register("Text", typeof(UserControl), typeof(UserControl), new PropertyMetadata(null));
This article gives a nice overview - I know it says Silverlight but it uses the same basic approach.
Related
I have read a very nice tutorial about letting ViewModels do their stuff while the views just switch themselves via databinding with DataTemplates.
I succesfully made a window which has an ApplicationViewModel, which in turn has a hardcoded DummyViewModel as its CurrentScreen property
<Window x:Class="MyProject.Aplication"
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:vm="clr-namespace:MyProject.ViewModels"
xmlns:v="clr-namespace:MyProject.Views"
xmlns:System="clr-namespace:System;assembly=mscorlib"
Title="Killer Application" Height="900" Width="1440"
WindowState="Maximized">
<!-- This view has its viewmodel as data context, which has a CurrentScreen property -->
<Window.DataContext>
<vm:AplicationViewModel/>
</Window.DataContext>
<Window.Resources>
<!-- each ViewModel will explicitly map to its current View - while this should happen EXPLICITLY, no? -->
<DataTemplate DataType="{x:Type vm:DummyViewModel}">
<v:DummyView/>
</DataTemplate>
<!-- Doc, are you telling me I'll have to fill this up for EVERY VIEW/VIEWMODEL? -->
<DataTemplate DataType="{x:Type vm:YetAnotherViewModel}">
<v:YetAnotherView/>
</DataTemplate>
</Window.Resources>
<!-- the content is bound to CurrentScreen property of the data context -->
<ContentControl Content="{Binding CurrentScreen}" />
</Window>
I would like to have some (very simple and direct, if possible) way to get the same result WITHOUT having to exhaustively code one DataTemplate Window Resource for every possible screen the window can show. Is there a way?
If you're doing MVVM, then you should be using an MVVM framework. For example, Caliburn.Micro will do view location and automatic binding based on conventions.
I resolved this by using the DataTemplateManager found in this article.
It enables to declare the View-to-ViewModel relation like this:
manager.RegisterDataTemplate<ViewModelA, ViewA>();
manager.RegisterDataTemplate<ViewModelB, ViewB>();
This is still explicit, but it reduces a LOT of overhead of having to do it in XAML. Think about namespaces for instance.
I am writing some data visualization code in WPF with MVVM Light. Here's a fragment:
<Window x:Class="EventBlockVisualization.MainWindow"
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:ignore="http://www.ignore.com"
Title="MainWindow"
mc:Ignorable="d ignore"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Window.Resources>
<ItemsPanelTemplate x:Key="GraphRowItemsPanelTemplate">
<StackPanel IsItemsHost="True" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</Window.Resources>
<Grid IsSharedSizeScope="True">
<ScrollViewer Margin="8" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True">
<ItemsControl x:Name="GraphItemsControl" Margin="8" ItemsSource="{Binding VibeEvents, Mode=OneTime}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="NameWidthSizeGroup" Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="NameTextBlock" Text="{Binding Name}" Grid.Column="0" Margin="4,0"/>
<ItemsControl x:Name="GraphRowItemsControl" ItemsSource="{Binding VibeEventViewModels, Mode=OneTime}" ItemsPanel="{DynamicResource GraphRowItemsPanelTemplate}" Grid.Column="1" Margin="4,0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Left" VerticalAlignment="Center" Height="10">
<TextBlock x:Name="FGTitleTextBox" Text="{Binding FGTitle}" Visibility="Collapsed"/>
<Button Margin="1,0,0,0" Width="{Binding LengthInSeconds}" HorizontalAlignment="Left" Background="{Binding BackgroundColor}" BorderBrush="#FF2186A1">
<Button.ToolTip>
<ToolTip>
<StackPanel>
<TextBlock FontWeight="Bold" Text="{Binding FGTitle}"/>
<TextBlock Text="{Binding LengthText}"/>
</StackPanel>
</ToolTip>
</Button.ToolTip>
</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Window>
I'd like to swap out the central ItemsControl.ItemTemplate DataTemplate and make it a User Control so that I can design it more easily in Expression Blend.
I cannot find a simple sample that includes a User Control in MVVM Light but there are some tutorial articles. For example in MVVM Instantiation Approaches (Option 6) Paul Stovell suggests binding in the UserControl's ViewModel in MVVM Light thus:
<UserControl ...>
<UserControl.Resources>
<ViewModelLocator x:Key="ViewModelLocator"/>
</UserControl.Resources>
<TextBox Text="{Binding Source={DynamicResource ViewModelLocator}, Path=CalculatorViewModel...}" />
That would work great when I'm designing the UserControl in Expression Blend as the locator can supply a ViewModel replete with dummy data. But what happens at runtime; how does that binding get overwritten with instances of the UserControl's ViewModel class supplied by collections in the main ViewModel? The same problem happens for the MainWindow at design time. If I am working in Expression Blend on the look and feel of the MainWindow how does that binding get overwritten with instances of the UserControl's ViewModel class supplied by collections in the design time main ViewModel?
There are a number of questions and answers that already touch on this:
In https://stackoverflow.com/a/3334780/575530 akjoshi suggests that the main ViewModel holds the instance of the UserControl's ViewModel; but how does that work when I am designing the UserControl itself?
In https://stackoverflow.com/a/9910298/575530 tam points out that "you want to keep your datacontext open and available for binding to in controls where you are using this control" and in the following comment SoMoS adds that one needs to "create properties in the ViewModel for the binded properties and when someone wants to change one property of the control (like some subcontrol enabled) he will have to go thru the View Model". That's promissing but I am not sure what to do in place of the MainViewModel's bindable collection of UserControlViewModels.
In https://stackoverflow.com/a/6340668/575530 Ehsan Ershadi suggests that "it's not a good idea to use MVVM Light ViewModelLocator for UserControles because it is a static property and when you are going to instantiate multiple instances of your user control there are going to have the same common ViewModel so they all act same and this is not what we want for a UserControl in case you decide to use it once in your entire project." And then states that "to get around this problem you need to modify the ViewModelLocator by making all the properties Non static for instance". I'm not sure how that would help me.
In the comments after https://stackoverflow.com/a/2637830/575530 Jon Mitchell mentions that "It does look like MVVM isn't ideal for creating user controls". I hope that's not right.
In contrast, in When should I use a UserControl instead of a Page? dthrasher mentions that "many of the WPF MVVM frameworks seem to avoid using the NavigationWindow and Page controls in favor of composing pages using nested UserControls", i.e. that UserControls are commonplace devices in MVVM.
In https://stackoverflow.com/a/1798649/575530 Reed Copsey reminds sandbox that "UserControls can always talk to their containing control via exposing properties and using DataBinding. This is very nice, since it preserves the MVVM style in all aspects." and that "The containing control can use properties to link two properties on two user controls together, again, preserving clean boundaries" But again I don't see how this helps when I am in Expression Blend designing the UserControl.
In Should I be using UserControls for my Views instead of DataTemplates? Rachel mentions occasionally using Expression Blend to design the UserControl before cutting and pasting the code into a DataTemplate: "in the event I do want to use it to design a DataTemplate, I usually create a new UserControl, design it the way I want it, then copy/paste the contents into a DataTemplate"
Sorry about this essay length question! I am confused about how to use MVVM Light when designing a UserControl destined to be the visual for the items in a collection on the MainWindow, especially how to set up the three bindings: run-time view models, design time view models for the main window and its instantiations of the user control, and a design time view model for the user control in isolation.
I think you're overcomplicating things:
What's wrong with:
<Grid IsSharedSizeScope="True">
<ScrollViewer Margin="8" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True">
<ItemsControl x:Name="GraphItemsControl" Margin="8" ItemsSource="{Binding VibeEvents, Mode=OneTime}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<MyShinyUserControl DataContext={Binding}/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
Bind each VibeEvent to the DataContext of the user control. In the user control itself I'd suggest creating a design-time DataContext to make design easier. Design-Time DataContext looks like this:
<UserControl x:Class="EMC.Windows.AlarmsModule.UserControl1"
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"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:AlarmsModule="clr-namespace:EMC.Windows.AlarmsModule" d:DesignHeight="300"
d:DesignWidth="300"
d:DataContext="{d:DesignInstance Type=AlarmsModule:Alarm}"
>
This gets you to a place where you can build your user control and have design-time data in it. And it's simple and doesn't require much, if any, scaffolding.
Based on Faster Solutions' answer here's the simplest example I can come up with of using a UserControl to display the contents of a list within MVVM Light.
For completeness sake I'll include all the code which I have tried to make as short as possible while still providing design time data that differs from run time data in both the user control's view model and the main view model.
Firstly the locator, VMUCExample/ViewModel/ViewModelLocator.cs:
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
namespace VMUCExample.ViewModel
{
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<ASquareViewModel>();
}
public ASquareViewModel ASquare
{
get
{
return ServiceLocator.Current.GetInstance<ASquareViewModel>();
}
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public static void Cleanup() {}
}
}
I've not used the data service side of MVVM Light, partly to keep things simple. The visible distinction between live data and design time data is handled in the two view model classes.
VMUCExample/ViewModel/ASquareViewModel.cs:
using System.Windows.Media;
using GalaSoft.MvvmLight;
namespace VMUCExample.ViewModel
{
public class ASquareViewModel : ViewModelBase
{
private Brush _SquareColour;
public Brush SquareColour
{
get
{
return _SquareColour ?? (_SquareColour = IsInDesignModeStatic ?
new SolidColorBrush(Color.FromArgb(0xFF, 0xFF, 0x78, 0x78)) : // FF7878 (pastel red)
new SolidColorBrush(Color.FromArgb(0xFF, 0xFF, 0xBB, 0x78))); // FFBB78 (nectarine)
}
set { _SquareColour = value; }
}
}
}
So looking at the user control in Expression Blend I see a simple rectangle with pastel red fill:
The main view model lives in the file VMUCExample/ViewModel/MainViewModel.cs:
using System.Collections.Generic;
using System.Windows.Media;
using GalaSoft.MvvmLight;
namespace VMUCExample.ViewModel
{
public class MainViewModel : ViewModelBase
{
private List<ASquareViewModel> _Squares;
public List<ASquareViewModel> Squares
{
get
{
if (_Squares == null)
{
_Squares = new List<ASquareViewModel>();
var colour = IsInDesignModeStatic ?
new SolidColorBrush(Color.FromArgb(0xFF, 0x78, 0xB2, 0xFF)) : // 78B2FF (pastel blue)
new SolidColorBrush(Color.FromArgb(0xFF, 0xF9, 0xFF, 0xC7)); // F9FFC7 (eggshell)
for (var i = 0; i < 10; i++)
{
_Squares.Add(new ASquareViewModel {SquareColour = colour});
}
}
return _Squares;
}
set { _Squares = value; }
}
public MainViewModel() {}
}
}
The view for this can also be edited in Expression Blend, but the view model code sets the design time colour data differently:
These are the two XAML files, firstly VMUCExample/ASquareUC.xaml:
<UserControl x:Class="VMUCExample.ASquareUC"
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" d:DesignHeight="15" d:DesignWidth="60"
d:DataContext="{Binding ASquare, Mode=OneWay, Source={StaticResource Locator}}">
<Grid>
<Rectangle Fill="{Binding SquareColour}" Margin="2" Width="50" Height="10"/>
</Grid>
</UserControl>
You can see I've used Faster Solutions' suggestion of putting d:DataContext so that the design time binding I need for Expression Blend when I am designing the user control does not block the data context I need at run-time nor the data context supplied by the parent when I am designing the main window in Expression Blend. I am uncomfortable about this though, it is not the approach characterised as MVVM Light in Paul Stovell's Option 6: A XAML View Model Locator and endorsed by #LBugnion
The other view file is VMUCExample\MainWindow.xaml:
<Window x:Class="VMUCExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vmucExample="clr-namespace:VMUCExample"
Height="200" Width="100"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Grid x:Name="LayoutRoot">
<ScrollViewer ScrollViewer.CanContentScroll="True">
<ItemsControl ItemsSource="{Binding Squares}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<vmucExample:ASquareUC/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Window>
The example application itself just draws ten rectangles in a column:
Too simple, I know, but hopefully shows the three potential data sets in use:
Design time for the user control (#FF7878 pastel red),
Design time for the user control set by the main window (#78B2FF pastel blue), and
Run time for the user control set by the main window (#F9FFC7 eggshell).
(N.B. There is another data option, run time for the user control not set by the main window. In this case the user control's view model chooses #FFBB78/nectarine but I didn't need that to explore these bindings.)
For completeness here is the VMUCExample\App.xaml file:
<Application x:Class="VMUCExample.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:VMUCExample.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
StartupUri="MainWindow.xaml"
mc:Ignorable="d">
<Application.Resources>
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
</Application.Resources>
</Application>
I have a RadBusyIndicator on my UserControl like so:
<Grid>
<!-- Other Content -->
<t:RadBusyIndicator IsBusy="{Binding IsBusy}"></t:RadBusyIndicator>
</Grid>
And whenever I click in the design view it goes to the BusyIndicator.
I can set the Panel.ZIndex to be negative to select the "Other Content", but this will cause the RadBusyIndicator to be behind the "Other Content"
I tried using a binding for the ZIndex like so:
<t:RadBusyIndicator Panel.ZIndex="{Binding BusyZIndex}" IsBusy="{Binding IsBusy}"></t:RadBusyIndicator>
But it doesn't help.
So the question is:
How do I have the RadBusyIndicator on "Top" of all the "Other Content" but still be able to click(in the designer) and go to the xaml line for that control?
The BusyIndicator needs to be "on top" to be in front of the controls. That makes it also on top in the designer.
There may be better ways of solving this, but what comes into mind is to make the BusyPanel a Resource on the UserControl, and then add it in the Grid control OnApplyTemplate or Loaded by code.
Here is the UserControl's XAML.
<UserControl x:Class="WpfApplication2.UserControl1"
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:t="REFERENCE.TO.THE.RAD.ASSEMBLY"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<t:RadBusyIndicator x:Key="TheBusyIndicator" IsBusy="{Binding IsBusy}">
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Button Content="Some content to the button"
Height="25"
Width="200"/>
</Grid>
</UserControl>
I have added the BusyIndicator as a Resource with the Key "TheBusyIndicator".
I have also added x:Name="LayoutRoot" to the Grid which will contain the BusyIndicator.
The Grid can of course have another name if it in fact is not the layout root control.
By adding the BusyIndicator to the Children collection last, it will appear in front of all other controls that are added by the markup code.
Here is the code
UserControl's Constructor:
public UserControl1()
{
this.Loaded += new RoutedEventHandler(UserControl1_Loaded);
}
The execting code:
private void UserControl1_Loaded(object sender, RoutedEventArgs e)
{
this.LayoutRoot.Children.Add(this.Resources["TheBusyIndicator"] as RadBusyIndicator);
}
I never use UserControls anymore, only CustomControls where the XAML goes to "Generic.xaml" and I have no editor to work in. So I have not seen this problem for a while.
Is there some way in WPF to get the same functionality DataTemplateSelector gives you, but for UserControls?
Say I have a StackView to which I want to bind an IEnumerable of objects. What I'd like to do is somehow have a mapping that, for each object type in the bound IEnumerable, looks at the object type and determines what UserControl to add to the StackView.
So, given three classes:
public class House : Building{}
public class Apartment : Building{}
public class Tent : Building{}
where each class inherits from Building and has its own defined UserControl, I'd like to set DataContext to an IEnumerable<Building> and somehow get the StackView to populate its set of children with the type-specific UserControl.
I'd like to do this with as little code behind as possible. The more data binding and XAML duct tape the better.
You can use complex user controls in a DataTemplate; just declare the DataTemplate as your UserControl.
Example:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4"
Title="MainWindow" Height="300" Width="300" Name="UI" >
<Window.Resources>
<DataTemplate DataType="{x:Type local:House}" >
<local:HouseUserControl DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Apartment}">
<local:ApartmentUserControl DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding ElementName=UI, Path=ListOfBuildings}" />
</Grid>
</Window>
I'm not sure I see the problem. Just create DataTemplates for each type in your resources somewhere and WPF will use them automatically to render each type.
I generate the XAML codes which actually describe a valid grid control - called the GridXAML. I want to create a grid object and add it to a stackpanel on my form.
How can I 1) create an object from its XAML string value, and 2) add it dynamically to a panel? Please help!
Giving a specific sample context as below.
The generated grid's xaml:
<Grid>
<Textblock Text="abb" />
</Grid>
The current main form of my WPF application.
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="333" Width="111">
<StackPanel x:Name="MyStackPanel" Orientation="Vertical">
<!--I want the grid appear here at runtime-->
</StackPanel>
</Window>
All helps are very much appreciated!
Use the XamlReader's XamlReader.Load() or XamlReader.Parse()