How to make custom control in WPF - c#

In winforms there were 2 templates: UserControl allows to make composite control (group of existing controls) to reuse it and standardize operations with it, CustomControl was a fully custom control (often rendered manually, performing differently than standard controls, etc), allowing overriding and provides access to protected things.
In wpf I only found UserControl template (using express edition of VS).
To create custom MyControl I am doing following:
create normal class, based on Control/ContentControl;
create resource dictionary with Style containing control template (could be only ControlTemplate, style is better, because can contain other properties setter)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MySolution">
<Style x:Key="MyControl" TargetType="local:MyControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyControl">
<Grid x:Name="PART_Grid">
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
bind them together like this
public class MyControl : Control
{
public MyControl()
{
var dictionary = new ResourceDictionary();
dictionary.Source = new Uri("/MySolution;component/MyControl.xaml", UriKind.Relative);
Style = dictionary["MyControl"] as Style;
Loaded += MyControl_Loaded;
}
}
define lazy parts accessors in template
private Grid _partGrid;
private Grid PartGrid
{
get
{
if (_partGrid == null)
_partGrid = (Grid)Template.FindName("PART_Grid", this);
return _partGrid;
}
}
It works, but I am not sure if its the most optimal way:
.cs and .xaml are separate files (not one entity as in case of wpf UserControl);
when moving xaml (to example, inside Controls folder) constructor code has to be adjusted;
parts accessor are not available in constructor (template is yet not loaded).
My question: is there better way to make custom control with template? More comfortable, automatic, with automatic parts accessors, etc.
Here are my wpf templates

It is better if you use the CustomControl VS template, not sure why you don't see it, maybe because you are using the Express version. That template generates a Generic.xaml file for you, there you define the styles/controltemplate. Also you will have the .cs with the c# definition of your control.
To get the diferent parts of your control, the recommended way is using the GetTemplateChild mehtod and pass the Part_Name and you normally do that overriding the OnApplyTemplate() method.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
ActionBtn = GetTemplateChild(BtnPartName) as ButtonBase;
ContentPopup = GetTemplateChild(PopupPartName) as Popup;
}
Take a look to this post to check a sample with the details of that implementation.

Related

WPF Setting Content for Custom Control

In my WPF app I have several labels on multiple pages that all look the same but may change style during program running (all at once).
After much searching online, I have gone through Window.Resources templates and User Controls (neither great for styling when changes are possible during program run) and have currently settled on CustomControl. However, I can't figure out how to set its Content. Some labels will have a wrap panel with mutiple grandchildren and some will just have text. However, I cannot work it out either way. This seems to be a program beyond Label. What am I missing?
My Control (just changed to inherit from Label):
public class MyControl : Label
{
static MyControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl), new FrameworkPropertyMetadata(typeof(MyControl)));
}
}
No changes to Themes.Generic.xaml
My Window XAML (styling here for testing purposes):
<local:MyControl Height="100">
<TextBlock Text="hi there" FontSize="60"></TextBlock>
<!-- More children will be needed, but lets start with one for now -->
</local:MyControl>
<local:MyControl Height="100" Content="this should exist" FontSize="50" />
How do I get that content to show up?
Edit: Removing the DefaultStyleKeyProperty.OverrideMetadata worked.
You should define a default template for your custom control if you're to override its DefaultStyleKeyProperty, in your Themes/Generic.xaml. Base it on the default template of Label:
<Style TargetType="{x:Type local:MyControl}" BasedOn="{StaticResource {x:Type Label}}"/>
Alternatively, you can remove the overriding of the DefaultStyleKeyProperty and leave your control like this:
public class MyControl : Label
{
// No overriding
}

Is it possible to tell a ControlTemplate resource to only apply itself to my inherited control?

I am using a DevExpress GridControl and I have inherited a class from that. The class is just a class in C# that adds some dependency properties, it has no XAML page.
In my ResourceDictionary, I have defined two elements: a ControlTemplate that adds some buttons to the grid's search control, and a Style that adjusts that search control's margin/padding properties. These are using Key identifiers that make them apply to every grid that I use. But I really only want them to apply when that grid is of my inherited type.
How do I make those two elements apply themselves only to my inherited control, and not the base grid control?
Current top tag definitions:
<ControlTemplate x:Key="{dxet:SearchControlThemeKey ResourceKey=Template, ThemeName=MyTheme}">
<Style x:Key="{dxgt:TableViewThemeKey ThemeName=MyTheme, ResourceKey=SearchPanelContentTemplate}"
TargetType="{x:Type ContentControl}">
Where dxet and dxgt are DevExpress namespaces.
The most straightforward solution would probably be to declare those resource inside of your custom grid control's default Style:
<Style TargetType="local:MyGridControl"
BasedOn="{x:Static dxg:GridControl}">
<!-- TODO: Double check the BasedOn style key above. -->
<!-- Put any *new* setters, triggers, etc. here. -->
<!-- You'll already inherit the setters and triggers from the BasedOn style. -->
<Style.Resources>
<!-- Resources only visible in the context of your custom grid's style: -->
<ControlTemplate x:Key="{dxet:SearchControlThemeKey ResourceKey=Template, ThemeName=MyTheme}" />
<Style x:Key="{dxgt:TableViewThemeKey ThemeName=MyTheme, ResourceKey=SearchPanelContentTemplate}"
TargetType="{x:Type ContentControl}" />
</Style.Resources>
</Style>
Put the above style in a Themes\Generic.xaml resource dictionary, located in the same assembly that declares your custom grid control. That's the standard location where WPF will probe for default control styles. If your AssemblyInfo.cs doesn't already contain such an entry, add this:
[assembly: ThemeInfo(
// Where theme specific resource dictionaries are located
// (used if a resource is not found in the page, or application
// resource dictionaries)
ResourceDictionaryLocation.None,
// Where the generic resource dictionary is located
// (used if a resource is not found in the page, app, or
// any theme specific resource dictionaries)
ResourceDictionaryLocation.SourceAssembly
)]
The result should be that instances of your custom grid will see the overridden resources, but instances of the standard GridControl will see the default versions.
Make sure you override the default style key for your custom grid:
static MyGridControl()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(MyGridControl),
new FrameworkPropertyMetadata(typeof(MyGridControl)));
}
Alternate Proposal
Did you consider using attached dependency properties to add new functionality to the existing grid control, instead of extending it with your own subclass?
Depending on what all you're doing, it might be better to declare a GridExtensions class that registers some attached properties, routed events, and class-level command handlers for your custom buttons.

How do I ensure that onApplyTemplate gets called before anything else

I have a wpf Custom Control on which I have been working. It has a shared New like this:
Shared Sub New()
'This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
'This style is defined in themes\generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(GetType(VtlDataNavigator_24), New FrameworkPropertyMetadata(GetType(VtlDataNavigator_24)))
ItemsSourceProperty.OverrideMetadata(GetType(VtlDataNavigator_24), New FrameworkPropertyMetadata(Nothing, AddressOf OnItemsSourceHasChanged))
End Sub
If an Items source has been set for the custom control this shared sub then invokes the overrideMetadata for the itemssource (as shown below)
Private Shared Sub OnItemsSourceHasChanged(ByVal d As DependencyObject, ByVal baseValue As Object)
Dim vdn As VtlDataNavigator_24 = DirectCast(d, VtlDataNavigator_24)
vdn.RecordCount = vdn.Items.SourceCollection.Cast(Of Object)().Count()
vdn.MyBaseCollection = DirectCast(vdn.ItemsSource, ICollectionView)
vdn.MyBaseEditableCollection = DirectCast(vdn.ItemsSource, IEditableCollectionView)
vdn.MyBaseCollection.MoveCurrentToFirst
vdn.RecordIndex = vdn.MyBaseCollection.CurrentPosition + 1
If Not IsNothing(vdn.FindButton) Then
If vdn.FindButton.Visibility = Visibility.Visible Then
vdn.RecordIndexTextBox.IsReadOnly = False
Else
vdn.RecordIndexTextBox.IsReadOnly = True
End If
End If
vdn.ResetTheNavigationButtons
vdn.SetupInitialStatesForNonNavigationButtons
End Sub
This then fails because buttons referred to in the code (and routines called from it) have not yet been instantiated because the override for OnApplyTemplate (shown below) has not been called.
Public Overrides Sub OnApplyTemplate()
MyBase.OnApplyTemplate()
RecordIndexTextBox = CType(GetTemplateChild("PART_RecordIndexTextBox"), TextBox)
RecordCountTextBox = CType(GetTemplateChild(RecordCountTextBoxPart), TextBox)
RecordTextBlock = CType(GetTemplateChild(RecordTextBlockPart), TextBlock)
OfTextBlock = CType(GetTemplateChild(OfTextBlockPart), TextBlock)
FirstButton = CType(GetTemplateChild(FirstButtonPart), Button)
PreviousButton = CType(GetTemplateChild(PreviousButtonPart), RepeatButton)
NextButton = CType(GetTemplateChild(NextButtonPart), RepeatButton)
LastButton = CType(GetTemplateChild(LastButtonPart), Button)
AddButton = CType(GetTemplateChild(AddButtonPart), Button)
CancelNewRecordButton = CType(GetTemplateChild(CancelNewButtonPart), Button)
EditButton = CType(GetTemplateChild(EditButtonPart), button)
CancelButton = CType(GetTemplateChild(CancelButtonPart), Button)
RefreshButton = CType(GetTemplateChild(RefreshButtonPart), Button)
SaveButton = CType(GetTemplateChild(SaveButtonPart), Button)
DeleteButton = CType(GetTemplateChild(DeleteButtonPart), Button)
FindButton = CType(GetTemplateChild(FindButtonPart), Button)
End Sub
If I add something along the lines of:
vdn.OnApplyTemplate
to OnItemsSourceHasChanged, OnApplyTemplate is called but nothing is resolved (see illustration below).
BUT if I don't set an itemssource on my control, then OnApplyTemplate gets called and the items resolve (see below)
Has anyone encountered this sort of behaviour before and found a way to correct it such that OnApplyTemplate is always the first thing to get called before anything that might require access to controls that have yet to be resolved.
Edit
The curious thing about this issue is that (and doesn't this always seem to be the case!) this was working until obviously I did something or set some property. What I am left with is a project that runs if I do not set an Items source on my custom control, and one which doesn't if I do because the custom handler I have in place to handle when the items source is changed on my custom control is running before OnApplyTemplate gets called.
Well I have at last been able to determine that my custom controls Itemssource property is being changed before the control is being drawn and rendered and therefore the code I have in place to set things up following the ItemsSource change raises null reference exceptions because the main control has yet to be rendered.
Given that it did work it must be something I've done but I'm now out od ideas as to how to delve into this further and actually find the reason. I'd welcome any suggestions you might have or potential work rounds.
Edit in relation to comments below: typical part of control template.
<!-- First Button -->
<Button Style="{StaticResource vtlNavButtonStyle}"
x:Name="PART_FirstButton"
Tag="First_Button"
Visibility="{Binding Path=NavigationButtonVisibility,Converter={StaticResource booltovis}, RelativeSource={RelativeSource TemplatedParent}}"
ToolTipService.ShowOnDisabled="False"
ToolTipService.ShowDuration="3000"
ToolTipService.InitialShowDelay="500">
<Button.ToolTip>
<Binding Path="FirstButtonToolTip"
RelativeSource="{RelativeSource TemplatedParent}"
TargetNullValue="{x:Static p:Resources.FirstText}">
</Binding>
</Button.ToolTip>
<StackPanel>
<Image Style="{StaticResource vtlImageStyle}">
<Image.Source>
<Binding Path="FirstImage"
RelativeSource="{RelativeSource TemplatedParent}">
<Binding.TargetNullValue>
<ImageSource>/VtlWpfControls;component/Images/16/first.png</ImageSource>
</Binding.TargetNullValue>
</Binding>
</Image.Source>
</Image>
</StackPanel>
</Button>
Calling OnApplyTemplate yourself isn't going to help; the framework will call it when the template has actually been applied. That said, the order in which things happen is not deterministic -- the template may or may not be applied before the ItemsSource is set. I'm working with UWP apps for Windows 10, which is a slightly different beast, but we've solved a similar issue doing something like this:
private TextBlock textBlock;
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Grab the template controls, e.g.:
textBlock = GetTemplateChild("MyTextBlock") as TextBlock;
InitializeDataContext();
DataContextChanged += (sender, args) => InitializeDataContext();
}
private void InitializeDataContext()
{
ViewModel ViewModel = DataContext as ViewModel;
if (viewModel != null)
{
// Here we know that both conditions are satisfied
textBlock.Text = ViewModel.Name;
}
}
The key is to not start listening for DataContextChanged until the template has been applied. If the data context has already been set, the first call to initializeDataContext takes care of things; if not, the callback takes care of things.
(In your case, replace our data context listening with items source listening, I suppose.)
This isn't an answer to your question, but instead expands on some things you mentioned in the comments.
I really think that it would benefit you to look into WPF commands as they pertain to custom controls. Your data navigator control sounds like it essentially supports a number of actions (go to first/previous/next/last; add; edit; cancel; etc) that you invoke using Button controls in the control template. Rather than looking for the buttons in OnApplyTemplate (at which point you store references to them so that you can presumably hook into their Click event later) you should support commands in your control: the buttons in the template would then bind to these commands.
An example would probably make this a bit clearer. The following is code for a custom control that supports two actions: go-to-first-page, and go-to-last-page. In the static constructor I register two command bindings, one for each action. These work by calling into a helper method that takes the command to "bind" to, plus a pair of delegates that get called when the action is invoked.
The commands I am using here are provided by the WPF framework, and are static properties contained in the static NavigationCommands class. (There are a bunch of other similar classes containing commands, just follow the links in the "See Also" section of that MSDN page).
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace StackOverflow
{
public class TestControl : Control
{
static TestControl()
{
RegisterCommandBinding<TestControl>(NavigationCommands.FirstPage,
x => x.GoToFirstPage());
RegisterCommandBinding<TestControl>(NavigationCommands.LastPage,
x => x.GoToLastPage(), x => x.CanGoToLastPage());
DefaultStyleKeyProperty.OverrideMetadata(typeof(TestControl),
new FrameworkPropertyMetadata(typeof(TestControl)));
}
void GoToFirstPage()
{
Console.WriteLine("first page");
}
void GoToLastPage()
{
Console.WriteLine("last page");
}
bool CanGoToLastPage()
{
return true; // Would put your own logic here obviously
}
public static void RegisterCommandBinding<TControl>(
ICommand command, Action<TControl> execute) where TControl : class
{
RegisterCommandBinding<TControl>(command, execute, target => true);
}
public static void RegisterCommandBinding<TControl>(
ICommand command, Action<TControl> execute, Func<TControl, bool> canExecute)
where TControl : class
{
var commandBinding = new CommandBinding(command,
(target, e) => execute((TControl) target),
(target, e) => e.CanExecute = canExecute((TControl) target));
CommandManager.RegisterClassCommandBinding(typeof(TControl), commandBinding);
}
}
}
The following is the control's default template. As you can see there are simply two Button controls, each one of which binds to the relevant command via its Command property (note this is not a data binding, ie. you're not using the {Binding} markup extension).
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow">
<Style TargetType="{x:Type local:TestControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TestControl}">
<StackPanel Orientation="Horizontal">
<Button Command="NavigationCommands.FirstPage" Content="First" />
<Button Command="NavigationCommands.LastPage" Content="Last" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Finally, here's the custom control in a Window. As you click the "First" and "Last" buttons you can see the actions being invoked by watching the relevant text appear in the debug console window.
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow">
<local:TestControl VerticalAlignment="Top" />
</Window>
If you use commands in this way then you should be able to simplify your control's code significantly.
I had a similar issue - a custom control (specifically, a class derived from Control) would show binding errors whenever a new instance of the control was instantiated. This was because the control template was being created before the bindings were setup. Once the bindings took effect, then the control would start to work.
To "fix" this (or work around it anyway) I just added a call to ApplyTemplate() to the control's constructor. So it ends up looking like this:
public CustomControl()
{
InitializeComponent();
ApplyTemplate();
}
Then there were no more binding errors.

Visual Studio Designer shows empty Window when using custom ContentPropertyAttribute

My application has a lot of windows and most of them share some basic features. Because of that I extended the Window class to create a base for all my windows.
Everything compiles and displays fine but the designer just shows an empty window when I use my window class.
I made a basic example that can be easily used, my real window is much more complex but this shows the problem.
Here is the code:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Markup;
namespace WpfApplication1
{
[ContentProperty("ContentElement")]
public class MyWindow : Window
{
public ToolBar ToolBar { get; private set; }
public StatusBar StatusBar { get; private set; }
public Border ContentBorder { get; private set; }
public UIElement ContentElement
{
get { return (UIElement)GetValue(ContentElementProperty); }
set { SetValue(ContentElementProperty, value); }
}
public static readonly DependencyProperty ContentElementProperty = DependencyProperty.Register(
"ContentElement", typeof(UIElement), typeof(MyWindow),
new PropertyMetadata(null, (d, e) =>
{
MyWindow w = (MyWindow)d;
w.ContentBorder.Child = (UIElement)e.NewValue;
}));
public MyWindow() : base()
{
ToolBar = new ToolBar();
ToolBar.Height = 30;
ToolBar.VerticalAlignment = VerticalAlignment.Top;
StatusBar = new StatusBar();
StatusBar.Height = 20;
StatusBar.VerticalAlignment = VerticalAlignment.Bottom;
ContentBorder = new Border();
ContentBorder.SetValue(MarginProperty, new Thickness(0, 30, 0, 20));
Grid grid = new Grid();
grid.Children.Add(ToolBar);
grid.Children.Add(ContentBorder);
grid.Children.Add(StatusBar);
Content = grid;
}
}
}
XAML example for using MyWindow:
<local:MyWindow 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">
<Grid>
<Rectangle Fill="Blue" />
</Grid>
</local:MyWindow>
Doing the exact same thing with a UserControl works just fine, also in the designer. Just replace every occurance of MyWindow with MyUserControl and extend from UserControl if you want to try that.
Is there any way I can get a custom Window like that to work with the designer, or do i have to make a UserControl and use that in every window?
Also, is this some kind of bug or intended behavior?
Addional info: I'm running Visual Studio 2015 Community and I'm using .net 4.6
I Also tried another approach. Instead of using the ContentPropertyAttribute i have overwritten the ContentProperty like this:
new public object Content {
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
new public static DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(object), typeof(BaseUserControl), new PropertyMetadata(null, (s, e) =>
{
MyWindow bw = (MyWindow)s;
bw.ContentBorder.Child = (UIElement)e.NewValue;
}));
Again this works completely fine with a UserControl. With a Window I can at least see the Content in the designer now, but the ToolBar and StatusBar are still not showing up in the designer. When running it everything works correctly.
First, I am no super expert on WPF, but have done a bunch and think I can offer and help clarify some components. First, you can NOT derive from a .XAML based declaration of a WPF-Window, it can only be if entirely within code. I have come to find that sometimes the visual element building is much easier to do in XAML than it is within code, but both can and do work.
So, that said, I would like to offer a solution that might work for you. Starting with WPF Window Style / Templatea, if you are not already familiar with them, along with other controls you can run through their defaults.
First, I am starting with a RESOURCE DICTIONARY STYLE definition that will mimic much of what you may want in your default form. This becomes the stuff within the "ControlTemplate" of the style definition. I have created this as a file "MyWindowStyle.xaml" at the root level WpfApplication1 I created on my machine (just to match your sample project file namespace reference).
Inside the template, you could have almost anything... grids, dock panel, stack panels, etc. In this case, I have used a DockPanel and added your sample ToolBar, StatusBar and two extra labels just for sample. I also preset size and bogus color just to give visualization of the parts when you confirm their impact.
The CRITICAL element to look at is the . This identifies where the content for each of your DERIVED Windows content will be placed... Think of it as a place-holder for each one of your forms for individuality while the rest of the form, its controls all remain consistent. You will see it come into play as you play around with it.
The content of it is and notice the style x:Key="MyWindowStyle". This coincidentally is the same as the xaml, but you could have 100's of styles within a single resource dictionary. I am keeping simple to just the one for demo purposes.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<Style x:Key="MyWindowStyle" TargetType="Window">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Grid>
<Grid.Background>
<SolidColorBrush Color="{DynamicResource WindowColor}"/>
</Grid.Background>
<AdornerDecorator>
<DockPanel LastChildFill="True" Background="Blue">
<!-- List items docked to the top based on top-most first going down -->
<ToolBar x:Name="tmpToolBar" Height="45" DockPanel.Dock="Top" />
<Label Content="Testing by Style"
Height="30" Width="150" DockPanel.Dock="Top"/>
<!-- When docking to the bottom, start with bottom most working up -->
<StatusBar x:Name="tmpStatusBar" Height="30"
Background="Yellow" DockPanel.Dock="Bottom" />
<Label Content="Footer area based from style"
Height="30" Width="250" DockPanel.Dock="Bottom" />
<!-- This one, since docked last is rest of the space of the window -->
<ContentPresenter DockPanel.Dock="Bottom"/>
</DockPanel>
</AdornerDecorator>
<ResizeGrip x:Name="WindowResizeGrip"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Visibility="Collapsed"
IsTabStop="false" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ResizeMode" Value="CanResizeWithGrip">
<Setter TargetName="WindowResizeGrip"
Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Next, we need to make this publicly available for the entire duration of the application, including availability within the designer mode... Within your projects "App.xaml" which is the startup for the application, it will have a default and empty area. Replace it with this.
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/WpfApplication1;component/MyWindowStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Now, to a CODE-ONLY (not a .xaml window based definition) of your "MyWindow.cs" class. If you look at the style where I declared the toolbar and statusbar, I assigned them the names of "tmpToolBar" and "tmpStatusBar" respectively. Notice the [TemplatePart()] declarations. I am now expecting the template to HAVE these controls by the given name within the TEMPLATE somewhere.
Within the constructor, I am loading the Style from the App.xaml resource dictionary being fully available. Then I follow-up with the OnApplyTemplate() which I typically heavily document my code so anyone following me has some idea how / where things originated from and self explanatory.
My entire "MyClass.cs" is below
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace WpfApplication1
{
[TemplatePart(Name = "tmpToolBar", Type = typeof(ToolBar))]
[TemplatePart(Name = "tmpStatusBar", Type = typeof(StatusBar))]
public class MyWindow : Window
{
protected ToolBar myToolBar;
protected StatusBar myStatusBar;
public MyWindow() : base()
{
// NOW, look for the resource of "MyWindowStyle" within the dictionary
var tryStyle = FindResource("MyWindowStyle") as Style;
// if a valid find and it IS of type Style, set the style of
// the form to this pre-defined format and all it's content
if (tryStyle is Style)
Style = tryStyle;
}
// the actual template is not applied until some time after initialization.
// at that point, we can then look to grab object references to the controls
// you have need to "hook up" to.
public override void OnApplyTemplate()
{
// first allow default to happen
base.OnApplyTemplate();
// while we get the style loaded, we can now look at the expected template "parts"
// as declared at the top of this class. Specifically looking for the TEMPLATE
// declaration by the name "tmpToolBar" and "tmpStatusBar" respectively.
// get object pointer to the template as defined in the style template
// Now, store those object references into YOUR Window object reference of Toolbar
var myToolBar = Template.FindName("tmpToolBar", this) as ToolBar;
if (myToolBar != null)
// if you wanted to add your own hooks to the toolbar control
// that is declared in the template
myToolBar.PreviewMouseDoubleClick += myToolBar_PreviewMouseDoubleClick;
// get object pointer to the template as defined in the style template
var myStatusBar = Template.FindName("tmpStatusBar", this) as StatusBar;
if (myStatusBar != null)
myStatusBar.MouseDoubleClick += myStatusBar_MouseDoubleClick;
// Now, you can do whatever else you need with these controls downstream to the
// rest of your derived window controls
}
void myToolBar_PreviewMouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// in case you wanted to do something based on PreviewMouseDoubleClick of the toolbar
MessageBox.Show("ToolBar: Current Window Class: " + this.ToString());
}
void myStatusBar_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// in case something for MouseDoubleClick on the StatusBar
MessageBox.Show("StatusBar: Current Window Class: " + this.ToString());
}
}
}
So now, lets put it into place. Have your application's main window derive from the MyWindow class. The only thing you need there is
namespace WpfApplication1
{
public partial class MainWindow : MyWindow
{}
}
In the DESIGNER of your form, put in a few controls, such as label, textbox, whatever. You do not see your actual other style yet, but just go with it. Save and run the sample app. Your main window should be displayed with the entire pre-defined template there ALONG WITH the few extra control you had placed specifically on this form.
Now, to get the full visualization in your "MainWindow" from the designer perspective. Within the .xaml area of
<my:MyWindow
x:Class="WpfApplication1.MainWindow"
[other declarations] >
just add the following before the close ">"
Style="{StaticResource MyWindowStyle}"
The resource is available via the App.xaml at startup and you should now be able to see the entire visual while you are designing... but you cant change the outermost template, just the content specific to this one page as mentioned about the "ContentPresenter" part of the template definition. What you are changing is within the place-holder portion allocated by the Template. If you want to change the main part of the window controls, you need to update the TEMPLATE!
But here is part of the trick of the template designer. Start with this, and build in what you need visually, get it placed right and once ready, take it out of here and put into the template and now it is applicable to the rest of all windows. Fix fonts, sizes, colors, whatever.
Hope this helps, and if you have any questions for follow-up, let me know.
Window class is very complex in compare to UserControl class. Microsoft has written more than 8k lines of code in Window class compare to 80 lines in UserControl, additionally Window class contain many operation/event/restriction on content property, and any one piece of code is hindering in rendering the content when you use [ContentProperty("ContentElement")] with the Window subclass MyWindow .
Probably making it a UserControl is better option, If not possible you can write some code temporarily(copy code from ContentElement property) in content property to see the design view.
<lib:MyWindow.Content>
<Button Content="Click" Width="200" />
</lib:MyWindow.Content>
and then just remove the code before run time. (Not a good idea but, whatever works.:) and I suspect that you have already figured that out.

How to work with template member properties

Before anything else, I do apologize for the following verbose question. Since I'm new to WPF, I decided to explain more in order to probably get more tips!
I have a UserControl like:
<UserControl x:Class="MyNamespace.MyUserControl2"...
xmlns:local="clr-namespace:MyNamespace"
Style="{DynamicResource ResourceKey=style1}">
<UserControl.Resources>
<Style x:Key="style1" TargetType="{x:Type UserControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type UserControl}">
...
<local:MyUserControl1 x:Name="myUserControl1" .../>
...
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
</UserControl>
To access to the myUserControl1 from the code behind, I used a property.
private MyUserControl1 _myUserControl1;
private MyUserControl1 myUserControl1
{
get
{
if (_myUserControl1 == null)
_myUserControl1 = this.Template.FindName("myUserControl1", this) as MyUserControl1;
return _myUserControl1;
}
}
(Is this a good approach to access a template member?)
On the other hand, there is a dependency property in MyUserControl2 class (say DP1) that is responsible for modifying one of myUserControl1 dependency properties. (Say SomeProperty)
private static void IsDP1PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as MyUserControl2;
if (instance != null)
{
instance.myUserControl1.SomeProperty = function(e.NewValue);
}
}
When I tried to run the above code, I noticed that instance.myUserControl1 is null. So I treated it like so:
if (instance != null && instance.myUserControl1 != null)
{
instance.myUserControl1.SomeProperty = function(e.NewValue);
}
Although this approach solved the issue, it causes myUserControl1.SomeProperty to remain uninitialized. So, I put the following code snippet at the loaded event to resolve it:
private void MyUserControl2_Loaded(object sender, RoutedEventArgs e)
{
this.myUserControl1.SomeProperty = function(DP1);
}
After that, I encountered another problem!
When I set some value to DP1 using setter attribute of a style, I received a null reference exception that says myUserControl1 property is still null at the loaded event. How can I work around it? -Thanks.
I guess you haven't still a clear idea about WPF.
You are hitting so many troubles because your approach is much "winforms-like", than functional. WPF makes your life harder if you insist using it in a imperative manner.
First off, the template represents a function to instructs the WPF engine on how to create the actual visual tree during the run time. You should use the name as reference within a template ONLY inside the hosting control (i.e. MyUserControl2), and getting the instance reference from within the OnApplyTemplate method. Nowhere else.
Example:
private MyUserControl1 _myUserControl1;
public override void OnApplyTemplate()
{
this._myUserControl1 = this.GetTemplateChild("myUserControl1") as MyUserControl1;
//here you should check whether the instance is actually set
}
The reference of any control is hosted should kept as private: no protected/internal/public exposition of any of the hosted controls.
Second point: how to bind two properties together.
Your goal is to "bind" a property of a control with another exposed by the hosted one. This task is absolutely normal, and it is one of the best features offered by WPF.
Supposing that the two properties share the same type, thus can be bound directly. Within your xaml:
<ControlTemplate TargetType="{x:Type UserControl}">
...
<local:MyUserControl1 x:Name="myUserControl1"
SomeProperty="{Binding Path=DP1, RelativeSource={RelativeSource Mode=TemplatedParent}}"
.../>
...
</ControlTemplate>
Note that: (1) SomeProperty must be a DependencyProperty, (2) must be writable, (3) DP1 must be also a DP, or -at least- notify any change via the INotifyPropertyChanged pattern.
The syntax depicted binds normally: SomeProperty = DP1, but not vice versa. If you need an bidirectional mapping, you should add "Mode=TwoWay" inside the "Binding" clause.
If you want to customize the function that maps the two properties, simply define your own converter via the IValueConverter interface, then declare it in the xaml.
Here you will find several useful info: http://msdn.microsoft.com/en-us/library/ms752347.aspx
Cheers

Categories