I feel like I'm missing a fairly fundamental concept to WPF when it comes to databinding, but I can't seem to find the right combination of Google keywords to locate what I'm after, so maybe the SO Community can help. :)
I've got a WPF usercontrol that needs to databind to two separate objects in order to display properly. Both objects must be dynamically set from an outside source. Thus far I've simply been using the DataContext property of the form for dynamic object binding, but that only allows for one object to be referenced. I feel like this is a simple problem and that I must be missing something obvious.
My previous attempt looks something like this:
<UserControl.Resources>
<src:Person x:Key="personSource" />
<src:Job x:Key="jobSource" />
</UserControl.Resources>
<TextBox Text="{Binding Source={StaticResource personSource}, Path=Name" />
<TextBox Text="{Binding Source={StaticResource jobSource}, Path=Address" />
This will bind to any defaults I give the classes just fine, but If I try to dynamically set the objects in code (as I show below) I don't see any change.
Person personSource = FindResource("personSource") as Person;
personSource = externalPerson;
Job jobSource= FindResource("jobSource") as Job;
jobSource = externalJob;
What am I missing?
I would probably use a CustomControl with two DependencyProperties. Then the external site that uses your custom control could bind the data that they want to that control, also by using a custom control you can template the way the control looks in different situations.
Custom control code would look something like:
public class CustomControl : Control
{
public static readonly DependencyProperty PersonProperty =
DependencyProperty.Register("Person", typeof(Person), typeof(CustomControl), new UIPropertyMetadata(null));
public Person Person
{
get { return (Person) GetValue(PersonProperty); }
set { SetValue(PersonProperty, value); }
}
public static readonly DependencyProperty JobProperty =
DependencyProperty.Register("Job", typeof(Job), typeof(CustomControl), new UIPropertyMetadata(null));
public Job Job
{
get { return (Job) GetValue(JobProperty); }
set { SetValue(JobProperty, value); }
}
static CustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl)));
}
}
Generic.xaml is a file that should be created for you and could have a Style that looks something like this:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication3">
<Style TargetType="{x:Type local:CustomControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<TextBox Text="{TemplateBinding Person.Name}" />
<TextBox Text="{TemplateBinding Job.Address}" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Finally, when you go to use your control you would do something like this.
<src:CustomControl Person="{Binding Person}" Job="{Binding Job}" />
The reason your text boxes don't update is that you are binding them to a StaticResource. As the name implies these resources are static and don't post change notifications. And because Binding is a MarkupExtension and does not derive from DependencyObject you can't use a DynamicResource.
Try creating depedency properties on your control to reference the Person and Job objects.
Then set the DataContext of the UserControl to reference itself.
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Then you can use dot notation to reference the required properties.
<TextBox Text="{Binding Path=Person.Name" />
<TextBox Text="{Binding Path=Job.Address" />
Or use the source parameter
<TextBox Text="{Binding Source=Person, Path=Name" />
<TextBox Text="{Binding Source=Job, Path=Address" />
Related
I have been unable to find a clean, simple, example of how to correctly implement a usercontrol with WPF that has a DependencyProperty within the MVVM framework. My code below fails whenever I assign the usercontrol a DataContext.
I am trying to:
Set the DependencyProperty from the calling ItemsControl , and
Make the value of that DependencyProperty available to the ViewModel of the called usercontrol.
I still have a lot to learn and sincerely appreciate any help.
This is the ItemsControl in the topmost usercontrol that is making the call to the InkStringView usercontrol with the DependencyProperty TextInControl (example from another question).
<ItemsControl ItemsSource="{Binding Strings}" x:Name="self" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="v:InkStringView">
<Setter Property="FontSize" Value="25"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the InkStringView usercontrol with the DependencyProperty.
XAML:
<UserControl x:Class="Nova5.UI.Views.Ink.InkStringView"
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"
x:Name="mainInkStringView"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding TextInControl, ElementName=mainInkStringView}" />
<TextBlock Grid.Row="1" Text="I am row 1" />
</Grid>
</UserControl>
Code-Behind file:
namespace Nova5.UI.Views.Ink
{
public partial class InkStringView : UserControl
{
public InkStringView()
{
InitializeComponent();
this.DataContext = new InkStringViewModel(); <--THIS PREVENTS CORRECT BINDING, WHAT
} --ELSE TO DO?????
public String TextInControl
{
get { return (String)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}
}
That is one of the many reasons you should never set the DataContext directly from the UserControl itself.
When you do so, you can no longer use any other DataContext with it because the UserControl's DataContext is hardcoded to an instance that only the UserControl has access to, which kind of defeats one of WPF's biggest advantages of having separate UI and data layers.
There are two main ways of using UserControls in WPF
A standalone UserControl that can be used anywhere without a specific DataContext being required.
This type of UserControl normally exposes DependencyProperties for any values it needs, and would be used like this:
<v:InkStringView TextInControl="{Binding SomeValue}" />
Typical examples I can think of would be anything generic such as a Calendar control or Popup control.
A UserControl that is meant to be used with a specific Model or ViewModel only.
These UserControls are far more common for me, and is probably what you are looking for in your case. An example of how I would use such a UserControl would be this:
<v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" />
Or more frequently, it would be used with an implicit DataTemplate. An implicit DataTemplate is a DataTemplate with a DataType and no Key, and WPF will automatically use this template anytime it wants to render an object of the specified type.
<Window.Resources>
<DataTemplate DataType="{x:Type m:InkStringViewModel}">
<v:InkStringView />
</DataTemplate>
<Window.Resources>
<!-- Binding to a single ViewModel -->
<ContentPresenter Content="{Binding MyInkStringViewModelProperty}" />
<!-- Binding to a collection of ViewModels -->
<ItemsControl ItemsSource="{Binding MyCollectionOfInkStringViewModels}" />
No ContentPresenter.ItemTemplate or ItemsControl.ItemTemplate is needed when using this method.
Don't mix these two methods up, it doesn't go well :)
But anyways, to explain your specific problem in a bit more detail
When you create your UserControl like this
<v:InkStringView TextInControl="{Binding text}" />
you are basically saying
var vw = new InkStringView()
vw.TextInControl = vw.DataContext.text;
vw.DataContext is not specified anywhere in the XAML, so it gets inherited from the parent item, which results in
vw.DataContext = Strings[x];
so your binding that sets TextInControl = vw.DataContext.text is valid and resolves just fine at runtime.
However when you run this in your UserControl constructor
this.DataContext = new InkStringViewModel();
the DataContext is set to a value, so no longer gets automatically inherited from the parent.
So now the code that gets run looks like this:
var vw = new InkStringView()
vw.DataContext = new InkStringViewModel();
vw.TextInControl = vw.DataContext.text;
and naturally, InkStringViewModel does not have a property called text, so the binding fails at runtime.
You're almost there. The problem is that you're creating a ViewModel for your UserControl. This is a smell.
UserControls should look and behave just like any other control, as viewed from the outside. You correctly have exposed properties on the control, and are binding inner controls to these properties. That's all correct.
Where you fail is trying to create a ViewModel for everything. So ditch that stupid InkStringViewModel and let whoever is using the control to bind their view model to it.
If you are tempted to ask "what about the logic in the view model? If I get rid of it I'll have to put code in the codebehind!" I answer, "is it business logic? That shouldn't be embedded in your UserControl anyhow. And MVVM != no codebehind. Use codebehind for your UI logic. It's where it belongs."
Seems like you are mixing the model of the parent view with the model of the UC.
Here is a sample that matches your code:
The MainViewModel:
using System.Collections.Generic;
namespace UCItemsControl
{
public class MyString
{
public string text { get; set; }
}
public class MainViewModel
{
public ObservableCollection<MyString> Strings { get; set; }
public MainViewModel()
{
Strings = new ObservableCollection<MyString>
{
new MyString{ text = "First" },
new MyString{ text = "Second" },
new MyString{ text = "Third" }
};
}
}
}
The MainWindow that uses it:
<Window x:Class="UCItemsControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:UCItemsControl"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<v:MainViewModel></v:MainViewModel>
</Window.DataContext>
<Grid>
<ItemsControl
ItemsSource="{Binding Strings}" x:Name="self" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="v:InkStringView">
<Setter Property="FontSize" Value="25"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<v:InkStringView TextInControl="{Binding text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Your UC (no set of DataContext):
public partial class InkStringView : UserControl
{
public InkStringView()
{
InitializeComponent();
}
public String TextInControl
{
get { return (String)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}
(Your XAML is OK)
With that I can obtain what I guess is the expected result, a list of values:
First
I am row 1
Second
I am row 1
Third
I am row 1
You need to do 2 things here (I'm assuming Strings is an ObservableCollection<string>).
1) Remove this.DataContext = new InkStringViewModel(); from the InkStringView constructor. The DataContext will be one element of the Strings ObservableCollection.
2) Change
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
to
<v:InkStringView TextInControl="{Binding }" />
The xaml you have is looking for a "Text" property on the ItemsControl to bind the value TextInControl to. The xaml I put using the DataContext (which happens to be a string) to bind TextInControl to. If Strings is actually an ObservableCollection with a string Property of SomeProperty that you want to bind to then change it to this instead.
<v:InkStringView TextInControl="{Binding SomeProperty}" />
I have built the following control:
MyContentControl.xaml.cs:
public partial class MyContentControl : UserControl
{
public MyContentControl()
{
InitializeComponent();
}
public static readonly DependencyProperty MyContentProperty = DependencyProperty.Register(
"MyContent", typeof(object), typeof(MyContentControl), new PropertyMetadata(default(object)));
public object MyContent
{
get { return (object) GetValue(MyContentProperty); }
set { SetValue(MyContentProperty, value); }
}
}
MyContentControl.xaml:
<UserControl x:Class="MyContentControl"
x:Name="self"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Border BorderBrush="Gray" BorderThickness="1">
<ContentPresenter Content="{Binding ElementName=self, Path=MyContent}"/>
</Border>
</Grid>
</UserControl>
And it can be used like this:
<controls:MyContentControl>
<controls:MyContentControl.MyContent>
<TextBox Text="Some text..."/>
</controls:MyContentControl.MyContent>
</controls:MyContentControl>
What I would like to achieve is being able to use my control like this:
<controls:MyContentControl>
<TextBox Text="Some text..."/>
</controls:MyContentControl>
I would like to define the inner content like I would e.g. for a StackPanel.
How can this be achieved?
You need to decorate the MyContentControl class with the ContentPropertyAttribute https://learn.microsoft.com/de-de/dotnet/api/system.windows.markup.contentpropertyattribute?view=net-5.0
[ContentProperty("MyContent")]
public partial class MyContentControl : UserControl
{
...
Then you should be able to directly add the content without explicitly specifying "<controls:MyContentControl.MyContent>" in Property Element syntax. So that the markup below should parse and be valid:
<controls:MyContentControl>
<TextBox Text="Some text..."/>
</controls:MyContentControl>
Although lidqy's answer was closest to what I had initially intended, I ended up following Andy's suggestion from the comments to the question.
The key to that approach is: the desired custom XAML is defined in a style/template, in any available resource dictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:MyControls">
<Style TargetType="{x:Type controls:MyContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:MyContentControl}">
<Grid>
<Border BorderBrush="Gray" BorderThickness="1">
<ContentPresenter/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
The MyContentControl class then only needs to inherit ContentControl - and does not need to be a UserControl (with .xaml and xaml.cs) files.
#Andy: Regarding the question of "named elements": the exception/error is "Error MC3093: Cannot set Name attribute value 'xxx' on element 'yyy'. 'yyy' is under the scope of element 'MyContentControl', which already had a name registered when it was defined in another scope.", pretty much like here.
#lidqy: I also thought "why not just use the Content property?", but when trying I apparently created some kind of recursive binding.
Thank you, both! :o)
NOTE: The above approach should work for all ContentControls, including UserControls, enabling re-templating of basically any custom control ;o)
I have a custom control (MediaPlayer) that contains 2 other custom controls, a media player (Host) and a control bar (UI).
This control in itself is quite simple, it just binds the two together for display.
Now the first problem I got is that I couldn't set Host or UI properties from MediaPlayer, so I duplicated all properties relevant at design-time and linked them via binding. Is this the right away of achieving this? It's kind of clunky but it works.
<Style TargetType="{x:Type local:MediaPlayerWpf}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MediaPlayerWpf}">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid x:Name="PART_HostGrid" Margin="0,0,0,46">
<!--Filled by SetPlayerHost -->
</Grid>
<local:PlayerControls x:Name="PART_MediaUI" Height="46" Width="Auto"
VerticalAlignment="Bottom" MouseFullscreen="{TemplateBinding MouseFullscreen}"
MousePause="{TemplateBinding MousePause}"
IsPlayPauseVisible="{Binding IsPlayPauseVisible, RelativeSource={RelativeSource TemplatedParent}}"
IsStopVisible="{TemplateBinding IsStopVisible}"
IsLoopVisible="{TemplateBinding IsLoopVisible}"
IsVolumeVisible="{TemplateBinding IsVolumeVisible}"
IsSpeedVisible="{TemplateBinding IsSpeedVisible}"
IsSeekBarVisible="{TemplateBinding IsSeekBarVisible}"
PositionDisplay="{TemplateBinding PositionDisplay}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This is a class for a generic media player. Then I have another Custom Control deriving from it that sets it to use a specific media player. (have one using MPV video player, and another one displaying a VapourSynth script output)
The derived class looks like this.
<Style TargetType="{x:Type local:VsMediaPlayer}" BasedOn="{StaticResource {x:Type ui:MediaPlayerWpf}}" />
Now the problem is I want to expose Script and Path properties as dependency properties so they can be databound. I can't take exactly the same approach as above, so how can I do it? The Host the Path and Script will be bound to is created at run-time within OnApplyTemplate.
I'm a bit confused about how to make this one work, and I'm not sure the first code above is the best solution. Thanks for any guidance.
I guess one option is to copy the base style template instead of inheriting from it, and I could initiate the Host class there instead of at run-time. Any other option?
Edit: Host property is declared like this in my generic MediaPlayer class, but I couldn't find a way to set its sub-properties (Host.Source) from the designer.
public static DependencyProperty HostProperty = DependencyProperty.Register("Host", typeof(PlayerBase), typeof(MediaPlayerWpf),
new PropertyMetadata(null, OnHostChanged));
public PlayerBase Host { get => (PlayerBase)GetValue(HostProperty); set => SetValue(HostProperty, value); }
private static void OnHostChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
MediaPlayerWpf P = d as MediaPlayerWpf;
if (e.OldValue != null)
P.HostGrid.Children.Remove(e.OldValue as PlayerBase);
if (e.NewValue != null) {
P.HostGrid.Children.Add(e.NewValue as PlayerBase);
P.TemplateUI.PlayerHost = e.NewValue as PlayerBase;
}
}
Edit: this is the XAML code of MediaPlayer
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:EmergenceGuardian.MediaPlayerUI">
<Style TargetType="{x:Type local:MediaPlayerWpf}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MediaPlayerWpf}">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<ContentPresenter x:Name="PART_HostGrid" Margin="0,0,0,46"
Content="{TemplateBinding Content}" />
<local:PlayerControls x:Name="PART_MediaUI" Height="46" Width="Auto"
VerticalAlignment="Bottom" MouseFullscreen="{TemplateBinding MouseFullscreen}"
MousePause="{TemplateBinding MousePause}"
IsPlayPauseVisible="{Binding IsPlayPauseVisible, RelativeSource={RelativeSource TemplatedParent}}"
IsStopVisible="{TemplateBinding IsStopVisible}"
IsLoopVisible="{TemplateBinding IsLoopVisible}"
IsVolumeVisible="{TemplateBinding IsVolumeVisible}"
IsSpeedVisible="{TemplateBinding IsSpeedVisible}"
IsSeekBarVisible="{TemplateBinding IsSeekBarVisible}"
PositionDisplay="{TemplateBinding PositionDisplay}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Adding x:FieldModifier="public" to PART_MediaUI throws "The attribute FieldModifier does not exist in namespace"
SOLUTION!!! After working with a few attached properties, I finally understand how they work, and attached properties are indeed the right solution. This will allow me to set UIProperties.IsVolumeVisible on the parent class. I just need to repeat that code for every property.
public static class UIProperties {
// IsVolumeVisible
public static readonly DependencyProperty IsVolumeVisibleProperty = DependencyProperty.RegisterAttached("IsVolumeVisible", typeof(bool),
typeof(UIProperties), new UIPropertyMetadata(false, OnIsVolumeVisibleChanged));
public static bool GetIsVolumeVisible(DependencyObject obj) => (bool)obj.GetValue(IsVolumeVisibleProperty);
public static void SetIsVolumeVisible(DependencyObject obj, bool value) => obj.SetValue(IsVolumeVisibleProperty, value);
private static void OnIsVolumeVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (!(d is MediaPlayerWpf P))
return;
P.UI.IsVolumeVisible = (bool)e.NewValue;
}
}
I found a partial solution. Instead of inheriting MediaPlayer from Control, I inherit from ContentControl.
In MediaPlayer Generic.xaml, I display the content like this right above the UI controls
<ContentPresenter x:Name="PART_HostGrid" Margin="0,0,0,46" Content="{TemplateBinding Content}" />
Override property metadata to ensure content is of type PlayerBase and to pass the content reference to the UI control
static MediaPlayerWpf() {
ContentProperty.OverrideMetadata(typeof(MediaPlayerWpf), new FrameworkPropertyMetadata(ContentChanged, CoerceContent));
}
public override void OnApplyTemplate() {
base.OnApplyTemplate();
UI = TemplateUI;
UI.PlayerHost = Content as PlayerBase;
}
private static void ContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
MediaPlayerWpf P = d as MediaPlayerWpf;
if (P.TemplateUI != null)
P.TemplateUI.PlayerHost = e.NewValue as PlayerBase;
}
private static object CoerceContent(DependencyObject d, object baseValue) {
return baseValue as PlayerBase;
}
And then I can use it like this
<MediaPlayerUI:MediaPlayerWpf x:Name="Player" IsVolumeVisible="False" IsSpeedVisible="False" IsLoopVisible="False" PositionDisplay="Seconds">
<VapourSynthUI:VsMediaPlayerHost x:Name="PlayerHost" />
</MediaPlayerUI:MediaPlayerWpf>
The advantage is that I no longer need to inherit from MediaPlayerWpf so there are less controls to manage.
However, I still need to duplicate UI properties to expose them to the designer, haven't found a way to access them in any other way.
Setting x:FieldModifier="public" in Generic.xaml results in "The attribute 'FieldModifier' does not exist in XML namespace"
UI is exposed as a dependency property like this. The designer allows to set UI="..." but not UI.IsVolumeVisible="false" nor recognizes < local:UI>. Is there a way to expose it from within a custom control?
public static DependencyPropertyKey UIPropertyKey = DependencyProperty.RegisterReadOnly("UI", typeof(PlayerControls), typeof(MediaPlayerWpf), new PropertyMetadata());
public static DependencyProperty UIProperty = UIPropertyKey.DependencyProperty;
public PlayerControls UI { get => (PlayerControls)GetValue(UIProperty); private set => SetValue(UIPropertyKey, value); }
I gave a comment above about how you could use a DependencyProperty and set it that type etc. This is all good but may be overkill for what you need. Just use the x:FieldModifier="public" to get what you're looking for.
Here's an example:
I make 3 user controls and have my MainWindow. The user controls are MainControl, SubControlA, SubControlB.
In MainControl I first give the controls a logical name and then FieldModifier to public.
<UserControl x:Class="Question_Answer_WPF_App.MainControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Question_Answer_WPF_App"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<local:SubControlA x:Name="SubControlA" x:FieldModifier="public"/>
<local:SubControlB x:Name="SubControlB" x:FieldModifier="public"/>
</StackPanel>
</UserControl>
Then I place that MainControl in my MainWindow and use it like so:
<Window x:Class="Question_Answer_WPF_App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Question_Answer_WPF_App"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<local:MainControl>
<local:SubControlA>
<TextBlock Text="I'm in SubControlA" />
</local:SubControlA>
</local:MainControl>
</Grid>
</Window>
Hope this helps. The idea is you can also reference the DependencyPropertys from those controls also like Visibility etc. (or whatever you were using in yours in the question.)
This is just an example as I wouldn't recommend doing it this cheap.
Ok, to follow up the answer from your comments / questions below let me explain how it works a little deeper. First, the SubControlA and SubControlB are just two empty UserControls that I made to have the example work.
In xaml anything between brackets gets initialized at that point. We use the namespace / type name to target the property and whatever is between the brackets goes to the setter of that property.
Consider this MainWindow... All I do is place a custom UserControl in it and it looks like this in xaml
<Window x:Class="Question_Answer_WPF_App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Question_Answer_WPF_App"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<local:ExampleControl />
</Window>
… and it looks like this in when ran
Now to see the custom ExampleControl because so far no big deal.
<UserControl x:Class="Question_Answer_WPF_App.ExampleControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:Media="clr-namespace:System.Windows.Media;assembly=PresentationCore"
xmlns:Windows="clr-namespace:System.Windows;assembly=PresentationCore"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<StackPanel>
<Button Visibility="Visible"
Height="50"
Background="Blue"
Content="Button A"/>
<Button>
<Button.Visibility>
<Windows:Visibility> Visible </Windows:Visibility>
</Button.Visibility>
<Button.Height>
<System:Double> 50 </System:Double>
</Button.Height>
<Button.Background>
<Media:SolidColorBrush>
<Media:SolidColorBrush.Color>
<Media:Color>
<Media:Color.R> 0 </Media:Color.R>
<Media:Color.G> 0 </Media:Color.G>
<Media:Color.B> 255 </Media:Color.B>
<Media:Color.A> 255 </Media:Color.A>
</Media:Color>
</Media:SolidColorBrush.Color>
</Media:SolidColorBrush>
</Button.Background>
<Button.Content> Button B </Button.Content>
</Button>
</StackPanel>
</UserControl>
In this ExampleControl I have two identical buttons, except one says Button A and the other Button B.
Notice how I referenced the properties in the first button via the properties name directly (which is mostly used) but I reference it by namespace / type for the second one. They have the same results...
Also notice that I had to include the reference to the namespace for certain types like:
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:Media="clr-namespace:System.Windows.Media;assembly=PresentationCore"
xmlns:Windows="clr-namespace:System.Windows;assembly=PresentationCore"
XAML has a built in parser that takes the string you send and attempts to designate initialize it as the type needed for the property. See how this works for enums (Visibility : System.Windows.Visibility), primitives (Height : System.Double), and unique objects like (Background : System.Windows.Media.Brush).
Also notice that Background being a Brush type can be of any type that derives from Brush. In the example I use a SolidColorBrush which has a base of Brush.
However; I also take it a step further in the Background. Notice that not only do I assign the SolidColorBrush but I assign the Color property of the SolidColorBrush as well.
Take your time to understand how the xaml is parsing and using these features and I believe it'll answer your question about how I'm referencing SubControlA and SubControlB from my MainControl at the beginning of this answer.
Following situation: I got a base class which provides a little framework for making modal dialogs with an adorner.
The base class has a property of type DataTemplate which contains the actual input scheme (all kinds of input are possible) as well as an object property which contains the mapping model (a model class to which the template binds it's input values).
Because I want to reuse the adorner, I made it have a ContentControl which anon has a ContentTemplate with the actual dialog design. The dialog's design contains a ContentControl whose Template is bound to the property in the adorner class. The DataContext of the adorner's ContentControl is set to itself, of course.
Now the embedded ContentControl (in the design) generates the DataTemplate and displays (in the current case) a TextBox. This TextBox now should be bound to the model. Therefore I reused the DataContext of the adorner design template for the actual input template. Here's how I've done it:
The adorner's ControlTemplate
<Border Grid.Row="0" Background="{DynamicResource InputAdornerHeaderBackground}" BorderThickness="0,0,0,1" CornerRadius="0">
<TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Stretch" TextWrapping="Wrap" Text="{Binding Header}"
FontSize="{DynamicResource InputAdornerHeaderFontSize}" Foreground="{DynamicResource InputAdornerHeaderForeground}"
FontWeight="{DynamicResource InputAdornerHeaderFontWeight}" Margin="8" />
</Border>
<Border Grid.Row="1" BorderThickness="1,0,1,1" BorderBrush="{DynamicResource InputAdornerBorderBrush}" CornerRadius="0">
<ContentControl ContentTemplate="{Binding InputControlTemplate}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</Border>
</Grid>
</ContentTemplate>
The actual input template (DataTemplate)
<DataTemplate x:Key="TextInputTemplate">
<Grid Background="Black" DataContext="{Binding DataContext.InputMapping, RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}}">
<TextBox Text="{Binding Path=Text, Mode=OneWayToSource}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="0"
adorners:InputAdorner.FocusElement="{Binding RelativeSource={RelativeSource Self}}" />
</Grid>
</DataTemplate>
Model class
public sealed class TextInputModel
{
public string Text { get; set; }
}
Adorner properties
public DataTemplate InputControlTemplate
{
get { return _inputControlTemplate; }
private set
{
if (Equals(value, _inputControlTemplate)) return;
_inputControlTemplate = value;
OnPropertyChanged();
}
}
public object InputMapping
{
get { return _inputMapping; }
private set
{
if (Equals(value, _inputMapping)) return;
_inputMapping = value;
OnPropertyChanged();
}
}
FYI: The model is being dynamically instantiated when the Adorner is being created. It does not get set twice. This must be some kind of binding issue.
The template shows correctly. I see and can input stuff into the textbox, but once I fetch the model all properties are default (""). It did work one or two times but somehow design changes have obviously made it disfunctional.
I don't get what is interfering here as from my point of view all should be set up correctly. I checked the context of the DataTemplate: It is the actual model class. Yet the textbox inputs do not update the property.
EDIT:
For some reason it seems that the attatched property is causing this issue. But why is it interfering? It does not override the DataContext, does it?
I would like to create a custom control that provides all the functionality of the DockPanel, but it also exposes a secondary Overlay that is "outside" of the DockPanel. There would be a dependency property that will control the visibility of the the overlay panel, such that when the property is set true/visible, the Panel will appear overlayed on top of everything within the DockPanel.
Ideally the consumer would be able to drop the control into the same situation as a normal DockPanel, and with no other changes it would behave just like the normal DockPanel:
<DockPanelWithOverlay LastChildFill="True" >
<Button DockPanel.Dock="Bottom".../>
<Button DockPanel.Dock="Top".../>
<Grid>
<Grid controls.../>
</Grid>
</DockPanelWithOverlay>
However, there would be available the secondary area into which they could place the additional content and invoke when required.
<DockPanelWithOverlay LastChildFill="True" >
<Button DockPanel.Dock="Bottom".../>
<Button DockPanel.Dock="Top".../>
<Grid>
<Grid controls.../>
</Grid>
<DockPanel.Overlay>
<whatever controls for the overlay>
</DockPanel.Overlay>
</DockPanelWithOverlay>
But that wouldn't be valid since the Content is being set twice? So to cope, when using the overlay I guess I would have to explicitly state what goes where?:
<DockPanelWithOverlay LastChildFill="True" >
<DockPanel.Children>
<Button DockPanel.Dock="Bottom".../>
<Button DockPanel.Dock="Top".../>
<Grid>
<Grid controls.../>
</Grid>
</DockPanel.Children>
<DockPanel.Overlay Visibility="{Binding IsVisible}">
<whatever controls for the overlay>
</DockPanel.Overlay>
</DockPanelWithOverlay>
I'm not entirely sure the best way to tackle this: whether to create a CustomControl, or a UserControl, inherit directly from the DockPanel and try to expose a separate ContentControl, or maybe inherit from Panel and delegate the MeasureOverride and ArrangeOverride to the DockPanel.
How should I tackle this?
Interesting question. I wrote a DockPanelWithOverlay component that does the work:
I chose here the CustomControl because I wanted to have inheritance of Panel.
But Panel doesn't have a template it can change.
So I wrote a Custom Control inheriting of Control with a custom template
But a Usercontrol would perfectly work I think (I didn't try to be honest)
Edit UserControl is not so good, because it inherits of ContentControl.
So it can only have one children.
The goal of the DockPanelWithOverlay is to have many children.
So I think UserControl is not the best inheritance, as often.
UserControl is better when you want to provide some content in xaml, mostly static, not customizable by user of control.
End of edit
To organize content inside the tempalte, I used a Grid.
Order of the two components matters.
It is the drawing order.
Grid allows to put two components at the same place :
Inside there'll be the Overlay control, and a underlying DockPanel.
DockPanelWithOverlay
..|
..|-ControlTemplate
......|
......|-Grid
..........|
..........|--DockPanel
..........|--OverlayControl
Having a template is easier to make some binding from the DockPanelWithOverlay to the template's controls properties.
(To generate a CustomControl, create a WPFCustom Control Library Project)
Excerpt of themes\generic.xaml in library :
<Style TargetType="{x:Type local:DockPanelWithOverlay}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:DockPanelWithOverlay}">
<!-- the grid allows to put two components at the same place -->
<Grid >
<DockPanel x:Name="dockPanel" />
<ContentControl x:Name="overlayControl" Visibility="{TemplateBinding OverlayVisibility}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Inheriting of control allows to use the template to create the small UIElements hierarchy.
Some dependency properties must be added for allowing binding :
Overlay for providing some UIElements, or a string for overlay content
OverlayVisibility for hiding/showing the overlay
Here is the code for the DockPanelWithOverlay :
(Note the OnApplytemplate called just after the templates componenets are called)
// Children is the property that will be valued with the content inside the tag of the control
[ContentProperty("Children")]
public class DockPanelWithOverlay : Control
{
static DockPanelWithOverlay()
{
// Associate the control with its template in themes/generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(typeof(DockPanelWithOverlay), new FrameworkPropertyMetadata(typeof(DockPanelWithOverlay)));
}
public DockPanelWithOverlay()
{
Children = new UIElementCollection(this, this);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// once the template is instanciated, the dockPanel and overlayCOntrol can be found from the template
// and the children of DockPanelWithOverlay can be put in the DockPanel
var dockPanel = this.GetTemplateChild("dockPanel") as DockPanel;
if (dockPanel != null)
for (int i = 0; i < Children.Count; )
{
UIElement elt = Children[0];
Children.RemoveAt(0);
dockPanel.Children.Add(elt);
}
}
// Here is the property to show or not the overlay
public Visibility OverlayVisibility
{
get { return (Visibility)GetValue(OverlayVisibilityProperty); }
set { SetValue(OverlayVisibilityProperty, value); }
}
// Here is the overlay. Tipically it could be a Texblock,
// or like in our example a Grid holding a TextBlock so that we could put a semi transparent backround
public Object Overlay
{
get { return (Object)GetValue(OverlayProperty); }
set { SetValue(OverlayProperty, value); }
}
// Using a DependencyProperty as the backing store for OverlayProperty.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty OverlayProperty =
DependencyProperty.Register("Overlay", typeof(Object), typeof(DockPanelWithOverlay), new PropertyMetadata(null));
public static readonly DependencyProperty OverlayVisibilityProperty =
DependencyProperty.Register("OverlayVisibility", typeof(Visibility), typeof(DockPanelWithOverlay), new PropertyMetadata(Visibility.Visible));
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public UIElementCollection Children
{
get { return (UIElementCollection)GetValue(ChildrenProperty); }
set { SetValue(ChildrenProperty, value); }
}
public static readonly DependencyProperty ChildrenProperty =
DependencyProperty.Register("Children", typeof(UIElementCollection), typeof(DockPanelWithOverlay), new PropertyMetadata(null));
}
Using the DockPanelWithOverlay :
<lib:DockPanelWithOverlay x:Name="dockPanelWithOverlay1"
OverlayVisibility="Visible"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Button Content="Top" Height="50" DockPanel.Dock="Top" Background="Red"/>
<Button Content="Bottom" Height="50" DockPanel.Dock="Bottom" Background="Yellow"/>
<Button Content="Left" Width="50" DockPanel.Dock="Left" Background="Pink"/>
<Button Content="Right" Width="50" DockPanel.Dock="Right" Background="Bisque"/>
<Button Content="Center" Background="Azure"/>
<lib:DockPanelWithOverlay.Overlay>
<Grid Background="#80404080">
<TextBlock Text="Overlay" FontSize="80" Foreground="#FF444444" HorizontalAlignment="Center" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="-15"/>
<TranslateTransform/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
</Grid>
</lib:DockPanelWithOverlay.Overlay>
</lib:DockPanelWithOverlay>
The overlay can easily be switched on or off binding from a CheckBox.IsChecked property for instance.
Here is the full working code : http://1drv.ms/1NfCl9z
I think it 's really the answer to your question. Regards
I suggest we should try to clarify how you see this working. My guess is that the secondary panel will be a DockPanel too, and will completely cover the main panel. I.e., you see either the one or the other, but never both. How do you envisage switching between the two? A ToggleButton perhaps? Or only under control of, say, some Trigger?
My first thought as to implementation is that you seem to like how DockPanel? lays things out, so why touch the layout methods? One way might be to have only one dockpanel, but two collections of children, which you set according to which you want to show. Or the secondary panel in a `Popup?
Do you want to be able to write something like this:
<DockPanelWithAlternative
AlternativeVisibility="{Binding somethingHere}" >
<TextBlock Dock.Top ... />
<TextBlock Dock.Alternative.Top ... />
</DockPanelWithAlternative>
What I am thinking of is something like:
<UserControl>
<Grid>
<DockPanel x:Name="MainPanel" ZIndex="0"/>
<DockPanel x:Name="AlternativePanel" Visbility=... ZIndex="1"/>
</Grid>
</UserControl>