Split one big XAML in number of Sub-XAML files - wpf

In my WPF4 Desktop-based application there is a big block with sidebar menu that repeats in each window and takes about 70 lines of XAML. In order to improve code reuse, I would like to split XAML file in two files:
XAML-file that contains code for sidebar menu (≈70 lines)
Base XAML file that contains «include/reference» to XAML-file with sidebar menu code
As I understood, there are two ways to implement my problem:
Use ResourceDictionary
Use UserControl/CustomControl
My questions:
What is the difference between ResourceDictionary and UserControl? Could you give me examples where I have to use UserControl and where ResourceDictionary?
Could you give a full code example how to include/import content of one XAML-file to other?
P.S. Here is an example of code that I want to export to separate XAML-file:
<Border Style = "{StaticResource Body_SideBarMenu_Border_Settings}">
<StackPanel Style = "{StaticResource Body_SideBarMenu}">
<TextBlock Style = "{StaticResource Body_SideBarMenu_Title}"
Text = "{x:Static res:Resources.WinApp_SideBarMenu_Title}" />
<TextBlock x:Name = "SideBar_WinReports"
Style = "{StaticResource Body_SideBarMenu_Item}"
Text = "{x:Static res:Resources.DashListMarker}">
<Hyperlink KeyboardNavigation.TabIndex = "12"
Style = "{StaticResource Body_SideBarMenu_Item_Hyperlink}"
Click = "Call_WinReports_Click">
<TextBlock Text = "{x:Static res:Resources.WinApp_ModuleName_Reports}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</Border>

ResourceDictionary is just a container for your styles/templates etc. So you really have a choice between using a style (and referencing it through a ResourceDictionary) or a UserControl.
In order to differentiate between the two, ask yourself a question: are you implementing just another look for some existing control, or you are implementing something really new, which is more than just a ListView (or a Border, or a ComboBox etc.)? In the former case, use a style; in the latter, create a new UserControl.
Specifically for your case, I would go for a UserControl.
Code example (although not full)
(Please note that a template for the following code can be inserted with VS's "add new UserControl")
Xaml:
<UserControl x:Class="SomeNamespace.SidebarMenu"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Resources> <!-- you can define local styles here -->
<Style x:Key="SidebarMenuTextblock" TargetType=TextBlock>
...
</Style>
</UserControl.Resources>
<Border Background=...>
<StackPanel>
<TextBlock
x:Name="Put_a_name_if_you_want_to_reference_this_item_in_code_behind"
Style="{StaticResource SidebarMenuTextblock}"
Text="{x:Static res:Resources.WinApp_SideBarMenu_Title}" />
...
</StackPanel>
</Border>
</UserControl>
.cs:
using System;
using System.Windows;
using System.Windows.Controls;
namespace SomeNamespace
{
public partial class SidebarMenu : UserControl
{
public NumericUpDown()
{
InitializeComponent();
}
...
// define here your properties etc,
}
}
Now, you can use the control like that:
<Window
x:Class="SomeOtherNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:SomeNamespace">
<Grid>
<controls:SidebarMenu PropertyIfYouDefinedOne="SomeValue"/>
...
</Grid>
</Window>

If you can get your hands on Expression Studio, in Expression Blend, you can simply right click on any control and convert it to an user control. As easy as that.
User controls are good for splitting the XAML file. In essence, it is used to redefine the behavior of an existing control.
However, with User Controls, you can define whole WPF Layout Controls and convert them to an User Control, with the children content inside them. This is very helpful for a project spread across multiple developers, and can also be used to emulate the behavior of an MDI, which is kind of absent in WPF.

Related

Skinning Control Backgrounds - Better Performance?

sorry if this question is overly simple, but I'm having a hard time figuring out how to create backgrounds to controls - in the hopes that it will improve app performance.
I have 9 different controls. All of them have a background. The backgrounds are made up of either images, other controls or both. All of those backgrounds have another background.
Think of this like Power Point with slides, slide layouts and slide masters - inherited in that order. I have 9 slides / controls.
The first 3 controls have the same "control layout" (let's call it
ControlLayout1). ControlLayout1 gets some of it's elements from ControlMaster1.
The second 3 controls also have the same control layout, but it is
different from the first. Let's call it ControlLayout2. It also
inherits from ControlMaster1.
The final set of 3 controls are different again. We can call them
ControlLayout3. But this time, they inherit from a different master - ControlMaster2.
Right now in each control I'm writing out all the XAML each time separately. I'm thinking there must be a way to not write these in each of these each item. Ideally, what I would like to create is one set of XAML that can be reused.
Here's some pseudo-XAML:
<UserControl x:Name="Control1">
<MyBackground (ControlLayout1)/>
</UserControl>
<UserControl x:Name="Control2">
<MyBackground (ControlLayout2)/>
</UserControl>
<UserControl x:Name="Control3">
<MyBackground (ControlLayout3)/>
</UserControl>
And then somewhere for ControlLayouts (I don't know, like Application.Resources or elsewhere)
<Canvas x:Name="ControlLayout1">
<MyMasterBackground (ControlMaster1)/>
</Canvas>
<Canvas x:Name="ControlLayout2">
<MyMasterBackground (ControlMaster1)/>
<TextBox Text="The Control 2">
</Canvas>
<Canvas x:Name="ControlLayout3">
<MyMasterBackground (ControlMaster2)/>
<TextBox Text="The Control 3">
</Canvas>
And then for the ControlMasters
<Canvas x:Name="ControlMaster1">
<Canvas.Background>
<ImageBrush ImageSource="/Images/image1.jpg" />
</Canvas.Background>
</Canvas>
<Canvas x:Name="ControlMaster2">
<Canvas.Background>
<ImageBrush ImageSource="/Images/image2.jpg" />
</Canvas.Background>
<TextBox Text="Control Master 1">
</Canvas>
Once defined, the ControlLayouts and ControlMasters never need to change - they are static.
Beyond just having a smaller XAP if I can put these all in one location and reuse the XAML, I'm hoping performance will be improved in my app as the ControlLayouts automatically get BitmapCached or something like that.
So first, is there a good strategy to implement the above (the ControlLayouts and Masters do not have any code-behind)? Secondly will performance be improved in loading of Control1, Control2, etc.? Finally, if they were pure usercontrols (i.e. they had some code behind), would that be better for performance?
Thanks in advance!
What you ask for is a combination of a few things:
About the Background thing: just create a dependency property (let's call it MyBackgroundDP) of type Brush in the code behind of a UserControl, and bind it to your XAML like:
<UserControl ...>
<Grid Background={"Binding MyBackgroundDP, RelativeSource={RelativeSource Mode=FindAncestor, AncestoryType=UserControl}}">
<!-- More XAML declarations -->
</Grid>
</UserControl>
To create the dependency property, you can use the built in snippet in visual studio: propdp
Simply write "propdp" and that TAB twice. Fill up the fields and it's all good.
Alright so that was easy enough, right? ;)
Now the tougher part: making so-called master pages.
Actually it's not that much different from the background thing.
Declare another dependency property, only this time of type object, or FrameworkElement (better).
Then in your XAML, you declare a kind of placeholder: ContentControl. Let's call it MyContentDP for this example:
<UserControl ...>
<Grid Background={"Binding MyBackgroundDP, RelativeSource={RelativeSource Mode=FindAncestor, AncestoryType=UserControl}}">
<ContentControl ContentTemplate="{Binding MyContentDP, RelativeSource={RelativeSource Mode=FindAncestor, AncestoryType=UserControl}}" />
</Grid>
</UserControl>
You can then fine tune whatever else you want to provide in this "master view", add a border around the Grid, put some flowers, you name it.
Once you're done, this is how you use it, assuming it was called MyUserControl
<Window ...
xmlns:local="...reference_to_your_usercontrol_dll/exe">
<Grid>
<local:MyUserControl MyBackgroundDP="Red">
<local:MyUserControl.MyContentDP>
<!-- More XAML declarations here -->
</local:MyUserControl.MyContentDP>
</local:MyUserControl>
</Grid>
</Window>
Now the performance point:
If you put all the XAML for this as a Custom control (which is DIFFERENT from a UserControl), you can then put all the XAML in your App.xaml
Why? because parsing XAML can be an intensive operation, and if you make WP7/SL parse it at runtime whenever you need it, you lose performance.
Instead, your App.xaml gets parsed at startup, then it's in memory. That's what's done in the loading of your application. You would get a performance boost, although it would be minimal for controls made of few XAML, it is still a good practice.
Hope this helps,
Bab.

WPF Dynamic Resource Lookup Behavior with FixedPage

Having the following very simple xaml:
<DocumentViewer Name="dv">
<FixedDocument Name="fd" Loaded="fd_loaded">
<FixedDocument.Resources>
<Style x:Key="TestStyle">
<Style.Setters>
<Setter Property="TextBlock.Foreground" Value="BlueViolet"/>
</Style.Setters>
</Style>
<SolidColorBrush x:Key="foregroundBrush" Color="Orange"/>
</FixedDocument.Resources>
<PageContent Name="pc">
<FixedPage Name="fp" Width="800" Height="600" Name="fp">
<TextBlock Name="tb" Style="{DynamicResource TestStyle}">
Lorem ipsum
</TextBlock>
<TextBlock Foreground="{DynamicResource foregroundBrush}" Margin="20">
Lorem ipsum
</TextBlock>
</FixedPage>
</PageContent>
</FixedDocument>
</DocumentViewer>
The use of Dynamic Resources (which I actually need in a more complex situation) here doesn't work. Using Static Resources colors the TextBlocks in the desired colors. Moving the Resources to the level of the FixedPage also does the trick. But I would like to have one generic resource dictionary on a top level element (because of runtime changes the user can make for colours, fonts, etc.). Placing the resources on Application level also does work. But it's not an option for good reasons.
Anybody have any clue why this doesn't work. Does it have something to do with the Logical Tree from the TextBlock upwards?
MSDN Resources Overview states that:
The lookup process checks for the requested key within the resource dictionary defined by the element that sets the property.
If the element defines a Style property, the Resources dictionary within the Style is checked.
If the element defines a Template property, the Resources dictionary within the FrameworkTemplate is checked.
The lookup process then traverses the logical tree upward, to the parent element and its resource dictionary. This continues until the root element is reached.
I also tried putting the Brush and the Style into the Resources of a (dummy) Style according to the above explanation of MSDN. But that didn't work either.
Really have the feeling that this can not be that complex, but most probably I oversee something. Any help is appreciated.
EDIT
When naming the TextBlock to "tb" and then using tb.FindResource("TestStyle") throws an exception. So that resource clearly can't be found. If I check out LogicalTreeHelper.GetParent(tb) and repeat that for the parents found I get the expected result: TextBlock > FixedPage > PageContent > FixedDocument ...
EDIT2
This works perfect. What's the difference with the XAML projected earlier?
<Window x:Class="WpfDynamicStyles2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<SolidColorBrush x:Key="resBrush" Color="Orange"></SolidColorBrush>
</Grid.Resources>
<StackPanel>
<Button>
<TextBlock Foreground="{DynamicResource resBrush}">Dummy text...</TextBlock>
</Button>
</StackPanel>
</Grid>
</Window>
EDIT3
private void fd_Loaded(object sender, RoutedEventArgs e)
{
Object obj = pc.TryFindResource("foregroundBrush");
obj = fp.TryFindResource("foregroundBrush");
obj = tb.TryFindResource("foregroundBrush");
}
The dynamic resource placed on the Foreground property of the textbox cannot be resolved (the actual resource is at the FixedDocument.Resources level). Also using the TryFindResource in code behind works from pc (PageContent) but from fp (FixedPage) and tb (TextBlock) it cannot resolve the resource (obj is null). When using a Static Resource in the XAML Markup everything works fine.
Well going the style way is a good design but not necessary. To make dynamic color based same named (keyed) brushes, we can use as dynamic color dictionaries (NOT brush dictionaries)
The solution can be as below ...
Create a single brush resource dictionary.
Refer the color in the brush with DynamicResource attribute.
Create multiple resource dictionaries with same keyed Color resource in each of them them.
Based on user's requirement, clear and add into Application.Current.resources.MergedDictionaries.
Example
Make a WPF project with a window that has following XAML....
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Window11Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<DockPanel LastChildFill="True">
<ComboBox DockPanel.Dock="Top" VerticalAlignment="Top"
SelectionChanged="ComboBox_SelectionChanged"
SelectedIndex="0">
<ComboBox.ItemsSource>
<Collections:ArrayList>
<System:String>Orange</System:String>
<System:String>Red</System:String>
<System:String>Blue</System:String>
</Collections:ArrayList>
</ComboBox.ItemsSource>
</ComboBox>
<DocumentViewer>
<FixedDocument>
<PageContent>
<FixedPage Width="793.76" Height="1122.56">
<TextBlock
FontSize="30"
Foreground="{StaticResource LabelColorBrush}"
Text="Test"/>
</FixedPage>
</PageContent>
</FixedDocument>
</DocumentViewer>
If you observe the window doesnt need to use anything which is dynamic. All refernces can remain static. So LabelColorBrush can be found in dictionary Window11Resources.xaml
In Window11Resources.xaml dictionary add a dynamic color brush.
<SolidColorBrush x:Key="LabelColorBrush"
Color="{DynamicResource DynamicColor}"/>
Add following 3 color brush dictionaries in some folder from your project...
<!-- Name = OrangeColorResource.xaml -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Color x:Key="DynamicColor">Orange</Color>
</ResourceDictionary>
<!-- Name = BlueColorResource.xaml -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Color x:Key="DynamicColor">Blue</Color>
</ResourceDictionary>
<!-- Name = RedColorResource.xaml -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Color x:Key="DynamicColor">Red</Color>
</ResourceDictionary>
Note that the key remains the same i.e. DynamicColor.
Now clear and recreate color dictionaries in App.Resources. I have done that in the code behind of Window.xaml.cs
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Application.Current.Resources.MergedDictionaries.Clear();
Application.Current.Resources.MergedDictionaries.Add(
new ResourceDictionary()
{
Source = new Uri("Resources\\"
+ ((ComboBox)sender).SelectedItem.ToString()
+ "ColorResource.xaml",
UriKind.RelativeOrAbsolute) });
}
Now as and when you select a color from the drop down, the dynamic color changes on the static resource brush. Note that there is no Style involved.
I hope this answers what you are asking.
By the way: the reason for this post had some more complex background. I really wanted to use a single resource dictionary whereby I could change colors, fonts, fontsizes, etc. dynamically during runtime (by an end-user). I am working on a tax form application. And the tax forms presented on screen for user input are resolving their resources at the Application level. So far so good.
But at the same time I want to present the user a print preview of the tax form where the same resourcedictionary (as object type, not as object instance) is used to define color schemes, fonts, fontsizes, etc. to be used for printing. Which can differ from the styling used for on screen user input. That's why I wanted to "attach" the resource dictionary on (for example) the FixedDocument level so that all pages in the document would refer to it. And when changing colors, fonts etc. all pages with common elements (TextBlocks, TextEditors, etc.) would respond to the change.
And then I became stuck...as described in this post.
For now I have a nice workaround by creating a specific instance of the resourcedictionary, storing it as a private variable and attach it to every individual page I put in the fixed document. It works and that already pleases me. But my programmers heart is still a little ached, because I would prefer to use a single instance of the resource dictionary and put it at some top level control so all controls in it can use it.
As a programmer we have to live with workarounds as well ;) If you have any further suggestions, I'm open to receive them. Thanx for your support thus far.
Greetz, Joep.
Also see: MSDN Forum Post about this issue

WPF User Control extending border class. "Does not support direct content"?

I am producing graphics for a process control system and I would like to create a system border which would visually wrap the various sub system being displayed in the process mimic. I could use a regular border for this except I want it to not only changing color reflecting system status, but also popping up small "balloons" indicating the piece of the system that is in alarm state.
I created a test project with a User Control and added a ListBox (for the balloons) and a ContentPresenter element wrapped in a border control. However, whenever I use this new control in another app, it wont allow me to add content to it. I've tried messing some with the ContentPropertyAttribute and properties of the ContentPresenter element, but I feel I am in the blind here.
<UserControl x:Class="SystemStatusBorder.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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Canvas Height="290" Width="303">
<Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ContentPresenter/>
</Border>
<ListBox Canvas.Right="0" Canvas.Bottom="0">
<ListBox.RenderTransform>
<TranslateTransform X="20"></TranslateTransform>
</ListBox.RenderTransform>
<ListBoxItem>TagA</ListBoxItem>
<ListBoxItem>TagB</ListBoxItem>
</ListBox>
</Canvas>
</UserControl>
I don't get it. What more should it need other than just the existence of a contentpresenter? UserControl subclasses ContentControl so I would have thought the wiring was in place. Eventually, I want it to be used something like this:
<SystemBorder>
<SystemBorder.MonitoredTags>
<List of relevant tags for the specific sub system goes here>
</SystemBorder.MonitoredTags>
<regular content goes here>
</SystemBorder>
To create your own container control, you must create a new custom control (not a UserControl).
Make your new control inherit from ContentControl.
Custom Controls don't have their own XAML. Instead, they are assigned a ControlTemplate.
When you create your first Custom Control, the IDE will create a new file Themes\Generic.xaml.
This is where the template for your control is. You can modify this template to match the XAML in your question. This will support the ContentPresenter element.
I found a very good walkthrough here.

Is it Possible to apply a DataTemplate to a Page?

I'm trying to follow the MVVM pattern laid out here: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090097 I have this in my MainWindowResources.xaml file:
<DataTemplate DataType="{x:Type vm:VendorsViewModel}">
<vw:Vendors/> <--- I get a "Can't put a page in a style" error in blend with this
</DataTemplate>
and I've got this in my MainWindow.xaml file
<Window.Resources>
<ResourceDictionary Source="MainWindowResources.xaml"/>
</Window.Resources>
The mainWindow.xaml file contains a menu on the left and page holder on the right. Can I apply a dataTemplate to a <Page>? Or does it have to be a <UserControl>? As it stands, nothing is being data bound, here's what I have on the page that I want to have the viewmodel applied to:
<Custom:DataGrid Margin="0,30,0,0" d:LayoutOverrides="Width" ItemsSource="{Binding Path=AllVendors, Mode=Default}" >
<Custom:DataGrid.Columns>
<Custom:DataGridTextColumn Header="Company Name" Binding="{Binding Path=Name}" />
</Custom:DataGrid.Columns>
</Custom:DataGrid>
DataTemplates are applied to Content, which in most cases is either the Content property of a ContentControl or the Items/ItemsSource property of an ItemsControl. Page is not derived from ContentControl (UserControl is) so a DataTemplate can't be applied to its Content.
From what you're doing here it doesn't sound like that's what you're trying to do though. It looks like you're trying to use a Page in a DataTemplate which is what the error is telling you. Page is treated like Window in that it is a root container that is intended to have visual Content defined in a xaml file. UserControl has a similar purpose but can be inserted anywhere into a layout. If you change vw:Vendors to be a UserControl that should get rid of this specific error but you should also consider whether you're gaining anything from having the UserControl instead of just putting its content directly into the DataTemplate - this can help discourage code-behind and force you to use your ViewModel correctly.

Is it good idea to mix ControlTemplate with user control?

Is it possible and a good idea to have user control (public MyControl: UserControl) which supports both ControlTemplates and existing content? I have understood that ControlTemplates should only be used when you inherit from Control (public MyControl: Control), but I found out that you can use them with UserControl too if your UserControl.xaml is empty.
Imagine I have control which has two rectangles side by side like the following:
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid ShowGridLines="true" Height="100" Width="100">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle Name="left" Grid.Column="0" Height="90" Fill="LightBlue"/>
<Rectangle Name="right" Grid.Column="1" Height="100" Fill="LightGreen"/>
</Grid>
</Page>
I would like the user of the control be able to replace those rectangles with whatever FrameworkElements he wants to use. So I need a ControlTemplate.
But in 99% of the cases user of the control is happy with the existing functionality so I would like him to be able to say:
Code behind:
mycontrol.Left.Fill = ....
XAML:
<mycontrol>
<mycontrol.Left.Fill = "Red"/>
</mycontrol>
That doesn't seem to be possible since if I support control templates I really don't have any UI elements or xaml. I only have the code behind file. I guess I could have a DependencyProperty Left but as long as I don't have some kind of container which would hold the content that would't do much good. I would have to create the grid in code behind file. Doesn't seem like a good idea.
And finally I would like to be able to use generics so the user can specify the type of the parts:
MyControl mycontrol<TLeft, TRight> = new MyControl<Rectangle, Button>();
This would help in code behind because of the type safety (no need to cast FrameworkElement into correct type). Unfortunately I don't think generics are really supported on the XAML side.
Is there any solution to this problem or is it really "Inherit from Control in order to support ControlTemplates but lose the easy usability of the control. Inherit from UserControl in order to support easy usability but lose the ControlTemplate support"?
Add a dependency property to the control:
public static DependencyProperty LeftFillProperty = DependencyProperty.
Register("LeftFill", typeof(Brush), typeof(MyControl));
public Brush LeftFill
{
get { return (Brush)GetValue(LeftFillProperty); }
set { SetValue(LeftFillProperty,value); }
}
Then in the default control template use:
<Rectangle Name="left" Grid.Column="0" Height="90" Fill="{TemplateBinding LeftFill}"/>
This will left you use (C#)
ctrl.LeftFill = Brushes.Red;
or (XAML)
<c:MyControl LeftFill="Red"/>
when you use the default template and when someone writes a new control template it's their responsibility to decide what to do with the LeftFill property (or to ignore it completely).
BTW, you should consider changing the names from "left" and "right" to something else ("MasterPanel" and "DetailPanel", "FoldersArea" and "FilesArea", whatever the logical usage in your control is), this will solve the issue of someone replacing the default template with a template that displays the same data in a different layout (top and bottom instead of left and right for example).

Resources