I'm looking to set a UserControl to be the Content of another UserControl in XAML, in the same way you can set a Button's Content to be anything.
Let's say my "outer" UserControl looks like this:
<MyUserControl>
<Grid>
<Border FancyPantsStyling="True">
<-- I want to insert other controls here -->
</Border>
</Grid>
</MyUserControl>
And I'd like to instantiate this way:
<local:MyUserControl>
<local:MyUserControl.Content>
<local:AnotherControl />
</local:MyUserControl.Content>
</local:MyUserControl>
How do I design MyUserControl to render it's Content in a specific location?
All the stuff you put into your UserControl's XAML is its Content so you can't inject something else by setting the Content property. There are a few different ways you could handle this. If you don't have anything in the code-behind for MyUserControl you can just get rid of it and use something like:
<ContentControl>
<ContentControl.Template>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid>
<Border FancyPantsStyling="True">
<ContentPresenter/>
</Border>
</Grid>
</ControlTemplate>
</ContentControl.Template>
<local:AnotherControl/>
</ContentControl>
If you have code behind that doesn't access the XAML elements directly you can do a similar thing with your existing control (since UC derives from ContentControl):
<local:MyUserControl>
<local:MyUserControl.Template>
<ControlTemplate TargetType="{x:Type local:MyUserControl}">
<Grid>
<Border FancyPantsStyling="True">
<ContentPresenter/>
</Border>
</Grid>
</ControlTemplate>
</local:MyUserControl.Template>
</local:MyUserControl>
If you need to keep the existing content connected to your code-behind you can use a DataTemplate to pass in the external content (into a new DP on MyUserControl) and apply that template to a ContentControl in the UC's XAML.
I got an idea, then tried it and it worked for me. I just wanted to share this to other people. I hope it will be useful.
The video link which explains what is the end of the solution: Video Link
The basic idea is to create UIElement DependencyProperty instead of creating Border DependencyProperty
Firstly, you should add your borders or panels or whatever you want to your user control (in your case it'S "MyUserControl") and make sure it has a name to access from .cs file:
<Border x:Name="LeftBorder" Grid.Column="0">
Then you should add a public UIElement value to your user control (in your case it's "MyUserControl"):
public UIElement LeftBorderChild
{
get { return (UIElement)GetValue(LeftBorderChildProperty ); }
set { SetValue(LeftBorderChildProperty , value); }
}
Secondly, type of your Dependencyproperty must be UIElement:
public static readonly DependencyProperty LeftBorderChildProperty = DependencyProperty.Register("LeftBorderChild", typeof(UIElement), typeof(MyUserControl), new PropertyMetadata(new PropertyChangedCallback(LeftBorderChildChanged)));
After these, typing events:
public static void LeftBorderChildChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyUserControl thisUserControl = d as MyUserControl;
thisCombobox._LeftBorderChildChanged(e); // Calling local event. The new child will be added in this local event function.
}
public void _LeftBorderChildChanged(DependencyPropertyChangedEventArgs e)
{
// In this function, new child element will be added to inside of LeftBorder
this.LeftBorder.Child = (UIElement)e.NewValue; // Sets left border child
}
We're done with this class. Let's call it from other class and add a control inside of it.
<local:MyUserControl Width="312" HorizontalAlignment="Right"
Margin="48, 0, 0, 0" VerticalAlignment="Center"
Height="56" >
<local:MyUserControl.LeftBorder>
<-- You can insert another control here -->
<-- Just don't remember that if you want to add more than one controls, you should add a panel then add controls into inside of the panel because Border child can only 1 child item -->
<StackPanel>
<-- Now you can insert your controls -->
</StackPanel>
</local:MyUserControl.LeftBorder>
</local:MyUserControl>
Note: When you do this firstly, you have to run your program before viewing in xaml designer. After running your program, all design systems are going to run synchronously.
I hope i understood what you mean and answered correctly.
Thank You
unless i misunderstood the question, you can use in your control and set its content to whatever you need.
Related
tl;dr: I want the consumer of my custom control to be able to define which separate resources two inner ContentPresenters of the custom control are to use. Side note: the ContentPresenters themselves will be part of DataTemplates, i.e. not explicit members of the custom control.
I'm creating a custom control that will use two ContentPresenter instances. The ContentTemplate used by each of these instances should be determined by defining DataTemplates using <DataTemplate DataType="{x:Type someType}">, i.e. the presenters will find the right template based on the data type.
As I understand, a ContentPresenter will search through static resources, starting with its' own, then traversing up the logical tree and, finally, checking for application-level resources.
Developers using the custom control should be able to specify a ResourceDictionary for each of the two ContentPresenter instances.
Just to illustrate my intention: if I were wanting to customize two DataTemplates for each of the presenters, I could so something like this:
CustomControl.xaml.cs snippet:
public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register(
nameof(HeaderTemplate), typeof(DataTemplate), typeof(DynamicDataGridControl), new PropertyMetadata(default(DataTemplate)));
public DataTemplate HeaderTemplate
{
get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
set { SetValue(HeaderTemplateProperty, value); }
}
public static readonly DependencyProperty CellTemplateProperty = DependencyProperty.Register(
nameof(CellTemplate), typeof(DataTemplate), typeof(DynamicDataGridControl), new PropertyMetadata(default(DataTemplate)));
public DataTemplate CellTemplate
{
get { return (DataTemplate)GetValue(CellTemplateProperty); }
set { SetValue(CellTemplateProperty, value); }
}
CustomControl.xaml snippet
<ContentPresenter x:Name="HeaderPresenter" ContentTemplate="{Binding ElementName=self, Path=HeaderTemplate}"/>
...
<ContentPresenter x:Name="CellPresenter" ContentTemplate="{Binding ElementName=self, Path=CellTemplate}"/>
And the others could use the custom control like this:
ConsumingControl.xaml snippet
<UserControl.Resources>
<DataTemplate x:Key="MyHeaderTemplate">
...
</DataTemplate>
<DataTemplate x:Key="MyCellTemplate">
...
</DataTemplate>
</UserControl.Resources>
<CustomControl HeaderTemplate="{StaticResource MyHeaderTemplate}" CellTemplate="{StaticResource MyCellTemplate}"/>
Now I would like to do basically the same, but instead of explicitly defining the ContentTemplate, I would like to define the ResourceDictionary or scope for each control.
I tried adding a DependencyProperty of type ResourceDictionary to my custom control and then referencing it as shown below, but it only leads to an exception: A 'Binding' cannot be set on the 'Source' property of type 'ResourceDictionary'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
<ContentPresenter>
<ContentPresenter.Resources>
<ResourceDictionary Source="{Binding ElementName=self, Path=CellResources}"></ResourceDictionary>
</ContentPresenter.Resources>
</ContentPresenter>
The reason for this is, that the types may be the same for both content presenters, but they should be rendered differently. I could achieve what I need (more or less) by specifying a converter, but I would much prefer to do it as outlined above. Is this possible?
Thanks in advance once again!
Is there a way to specify the "hole" for additional XAML content in WPF?
For instance if I create MySuperWindowBase and have some XAML in it with a layout, how can I specify where to place additional content in the layout in a subclass of MySuperWindowBase?
For a simplified example
MySuperWindowBase:
<Window>
<StackPanel>
<!-- Force child content here -->
</StackPanel>
</Window>
MyChildWindow:
<MySuperWindowBase>
<TextBlock>Place me in the StackPanel</TextBlock>
</MySuperWindowBase>
What you want is to give the base class window a template. Gusdor's answer explains everything but how to write a template that'll display the control's Content property, so here's an illustrative example of a control template for a window. The window's content is whatever's inside the <Window></Window> element in your ChildWindow.xaml file: <TextBlock>Place me in the StackPanel</TextBlock> is the placeholder content in your question. By default, that XAML visual tree fragment will get assigned to the window's Content property.
The ContentPresenter control presents the content. By default, it looks at the templated parent's Content property, but you can change that by setting the ContentPresenter's ContentSource property to the name of a different property of the templated parent.
<ControlTemplate TargetType="Window" x:Key="WindowBaseTemplate">
<Grid>
<Border
BorderBrush="Gray"
BorderThickness="1"
Margin="10"
Padding="20"
Background="GhostWhite"
>
<ContentPresenter
/>
</Border>
</Grid>
</ControlTemplate>
ContentPresenter looks like there must be magic, but it's just the defaults.
Create a new Style for the window -
https://code.msdn.microsoft.com/windowsdesktop/WPF-styling-a-Window-in-fcf4e4ce
Set the ContentTemplate property to include your mandatory elements.
Almost every other method will cause you to run into namescope issues at some point.
Situation:
I have a "wrapper panel" UserControl like this (namespaces and visual details removed for brevity):
<UserControl ...>
<Grid x:Name="LayoutRoot" Background="White">
<ContentPresenter x:Name="integratedPanelContent" Margin="5" />
</Grid>
</UserControl>
Then in the Code-behind I have registered a dependency property
public FrameworkElement PanelContent
{
get { return (FrameworkElement)GetValue(PanelContentProperty); }
set { SetValue(PanelContentProperty, value); }
}
public static readonly DependencyProperty PanelContentProperty =
DependencyProperty.Register("PanelContent", typeof(FrameworkElement), typeof(MyWrapperPanel),
new PropertyMetadata(null, OnPanelContentChanged));
private static void OnPanelContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyWrapperPanel)d).OnSetContentChanged(e);
}
protected virtual void OnSetContentChanged(DependencyPropertyChangedEventArgs e)
{
if (PanelContent != null)
integratedPanelContent.Content = PanelContent;
}
Now I can wrap any content into my control:
<my:MyWrapperPanel x:Name="myWrap">
<my:MyWrapperPanel.PanelContent>
<TextBlock x:Name="tbxNothing" Text="Nothing" />
</my:MyWrapperPanel.PanelContent>
</my:MyWrapperPanel>
Description of the problem:
Whenever I try to use the reference tbxNothing in codebehind, the system throws NullReferenceException because tbxNothing, although as a reference exists, does not point to the TextBlock defined in XAML, but is null.
Possible (but inconvenient) workaround:
There is a workaround where I remove x:Name from the TextBlock, and then I explicitely define private TextBlock called tbxNothing. Then in the OnNavigatedTo event handler I assign the value the following way:
tbxNothing = myWrap.PanelContent as TextBlock;
This works but is not a right way to do it, because if a content is a stackpanel that contains wanted controls, I'd have to traverse the tree to find what I need, which is extremely inconvenient.
Question:
Why is the textblock no longer visible when wrapped in a User control (the way described), and how to get it by its x:Name in code-behind?
The problem is your panel content is falling between two stools. On the one hand the content with the name "tbxNothing" is create in the namescope of the main page. However its not added to the object tree at that point. On the other hand the MyWrapperPanel being a UserControl has its own namescope and its into the object tree below this that the item with then name "tbxNothing" is added. FindName on the main page won't find anything inside the MyWrapperPanel because it has its own namescope and FindName on the MyWrapperPanel won't find "tbxNothing" because it doesn't exist in its namescope (being actually created in the main page).
The answer is don't use a UserControl as a basis for MyWrapperPanel. Instead create a Silverlight Template Control. Modify the base class it inherits from to ContentControl and tweak its default template to include a ContentPresenter. Should look something like this:-
public class MyWrapperPanel : ContentControl
{
public MyWrapperPanel ()
{
this.DefaultStyleKey = typeof(MyWrapperPanel );
}
}
then in themes/generic.xaml the style can look like this:-
<Style TargetType="local:MyWrapperPanel">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyWrapperPanel">
<Grid>
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Your main page xaml would look like:-
<my:MyWrapperPanel x:Name="myWrap">
<TextBlock x:Name="tbxNothing" Text="Nothing" />
</my:MyWrapperPanel>
Note that deriving from ContentControl gives you a Content property which the ContentPresenter auto-magically wires to.
Why won't this work?
In generic.xaml for a custom control:
In the style applied to the custom control...
<Setter Property="ChromeContent">
<Setter.Value>
<Grid />
</Setter.Value>
</Setter>
...
Later, in the control template...
<ContentPresenter Grid.Column="0"
x:Name="ChromeContentPresenter"
Content="{TemplateBinding ChromeContent}" />
Here's the dependency property for ChromeContent...
public Object ChromeContent
{
get { return (Object)GetValue(ChromeContentProperty); }
set { SetValue(ChromeContentProperty, value); }
}
public static readonly DependencyProperty ChromeContentProperty =
DependencyProperty.Register("ChromeContent", typeof(Object),
typeof(casPopup), null);
As you can see, it takes any object. I tried changing it to a Grid, but that did not help.
It throws this error (from javascript): _Failed to assign to property 'System.Windows.Controls.ContentPresenter.Content'
Oddly, the following will work fine if I remove the Grid from the setter nd just use text:
<Setter Property="ChromeContent" Value="DEFAULT" />
Also, this will work too from the OnApplyTemplate method in the control class:
Grid g = new Grid();
g.Width = 100;
g.Height = 25;
g.Background = new SolidColorBrush(Colors.LightGray);
ChromeContent = g;
I'm having a hard time understanding what is preventing the default content of a grid, defined in the generic.xaml from working. Does anyone have any knowledge on this matter?
Many thanks in advance for your help!
This is the problem:-
<Setter Property="ChromeContent">
<Setter.Value>
<Grid />
</Setter.Value>
</Setter>
You should not include a UIElement directly in a resource dictionary or as a value of a style. You might see the style as being some kind of descriptor but it isn't. The values in a style are constructed instances of the objects they hold. Your style holds a single instance of Grid. Whenever that style is used to assign to a ChromeContent property it will attempt to assing the same single instance of the Grid.
A UIElement can only be a child of one parent. What would happen if two instances your control were constructed? There would (if silverlight let you) be an attempt to assign the same single instance of the Grid to both controls.
This is one reason for templates such as ControlTemplate and DataTemplate. The markup inside these is invoked each time the template is used rather than when the Xaml is first parsed.
Edit:
To answer you supplementary question, you should default another property of type DataTemplate:-
<Setter Property="ChromeContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid />
</DataTemplate>
</Setter.Value>
</Setter>
Property:-
public Object ChromeContentTemplate
{
get { return (DataTemplate)GetValue(ChromeContentTemplateProperty); }
set { SetValue(ChromeContentTemplateProperty, value); }
}
public static readonly DependencyProperty ChromeContentTemplateProperty=
DependencyProperty.Register("ChromeContentTemplate", typeof(DataTemplate),
typeof(casPopup), null);
Control Template:-
<ContentPresenter Grid.Column="0"
x:Name="ChromeContentPresenter"
Content="{TemplateBinding ChromeContent}"
ContentTemplate="{TemplateBinding ChromeContentTemplate" />
Does anyone know why I can't set an event on a control template??
For example, the following line of code will not compile. It does this with any events in a control template.
<ControlTemplate x:Key="DefaultTemplate" TargetType="ContentControl">
<StackPanel Loaded="StackPanel_Loaded">
</StackPanel>
</ControlTemplate>
I am using a MVVM design pattern and the control here is located in a ResourceDictionary that is added to the application's MergedDictionaries.
Does anyone know why I can't set an event on a control template??
Actually, you can... But where would you expect the event handler to be defined ? The ResourceDictionary has no code-behind, so there is no place to put the event handler code. You can, however, create a class for your resource dictionary, and associate it with the x:Class attribute :
<ResourceDictionary x:Class="MyNamespace.MyClass"
xmlns=...>
<ControlTemplate x:Key="DefaultTemplate" TargetType="ContentControl">
<StackPanel Loaded="StackPanel_Loaded">
</StackPanel>
</ControlTemplate>
C# code :
namespace MyNamespace
{
public partial class MyClass : ResourceDictionary
{
void StackPanel_Loaded(object sender, RoutedEventArgs e)
{
...
}
}
}
(you might also need to change the build action of the resource dictionary to "Page", I don't remember exactly...)