How to prevent UserControl from breaking ElementName Bindings - c#

Edit:
How is the proper way to create a Control to avoid the following problem with the ElementName Binding:
<TextBox x:Name="MyTextBox" Text="some Text"></TextBox>
<Label>
<!--Binding works-->
<TextBlock Text="{Binding Path=Text, ElementName=MyTextBox, FallbackValue='Binding Failed'}"></TextBlock>
</Label>
<Button>
<!--Binding works-->
<TextBlock Text="{Binding Path=Text, ElementName=MyTextBox, FallbackValue='Binding Failed'}"></TextBlock>
</Button>
<local:MyUserControl>
<!-- THIS BINDING FAILS !!!-->
<TextBlock Text="{Binding Path=Text, ElementName=MyTextBox, FallbackValue='Binding Failed'}"></TextBlock>
</local:MyUserControl>
MyUserControl.xaml:
<UserControl x:Class="Problem.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" />
MyUserControl.xaml.cs
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
}
Original:
Im still new to WPF and could not find out how to do this properly.
I basically want a UserControl's child to retain the behavior of being able to bind to the root element's x:Name in a XAML.
This is an example that shows the problem caused by my UserControl Descriptor compared to the WPF Controls:
<Parent x:Name="_thisParent">
...
<Label>
<!-- Binding to _thisParent works -->
<TextBlock Text="{Binding Path=MyText, ElementName=_thisParent}" />
</Label>
<uc:Descriptor Text="description: ">
<!-- Binding to _thisParent FAILS !! -->
<TextBlock Text="{Binding Path=MyText, ElementName=_thisParent}" />
</uc:Descriptor>
<Button>
<!-- Binding to _thisParent works -->
<TextBlock Text="{Binding Path=MyText, ElementName=_thisParent}" />
</Button>
Here is the Code for my UserControl:
Descriptor.xaml
<UserControl
x:Class="EmbedContent.UserControls.Descriptor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="_thisDescriptor">
<UserControl.Template>
<ControlTemplate>
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="{Binding Path=Text, ElementName=_thisDescriptor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, FallbackValue='Binding Failed'}" />
<ContentPresenter Content="{Binding Path=Content, ElementName=_thisDescriptor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, FallbackValue='Binding Failed'}" />
</DockPanel>
</ControlTemplate>
</UserControl.Template>
Descriptor.xaml.cs
public partial class Descriptor : UserControl
{
#region Ctor
public Descriptor()
{
InitializeComponent();
}
#endregion
#region Dependency-Properties
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(Descriptor), new PropertyMetadata("Descriptor's default Text"));
#endregion
How would i need to implement a custom UserControl / ContentControl (if needed without xaml) to retain the behavior of the WPF Controls?
How is this done according to best practice anyway? I assume i only run into this problem because i'm doing something wrong.

Answer, including clarifying your decision: I finally found a solution.
Think about the problem a little deeper.
Binding a property of an element is part of the functionality of the element itself, not of its container.
Therefore, if you change the DataContext of an element, the default bindings are interpreted relative to it, not the DataContext of its container.
For this reason, you should not assign the DataContext inside a UserControl, as the default bindings behavior will change in a way that is unexpected for the user (programmer).
Now think about how ElementName bindings work.
For example, in the XAML that creates a new class from UserControl, you have defined named elements.
And then create multiple instances of UserControl.
In this case, if all elements with the same names were in the same visual tree, this would create a conflict, since it is not known which of them is being accessed.
(In this case, the "visual tree" is an oversimplification. It actually has to do with the system for registering the names of the UI elements).
To avoid such conflicts, when using XAML for ANY element (not only UserControl), its own local visual tree is created that is not associated with the main visual tree.
BUT!
I already wrote above, Bindings are part of the functionality of the element itself.
And therefore, the binding of a property of type ElementName will search for an element by name not higher in the container, but in the UserControl itself.
The same goes for Templates.
Therefore, the names of elements inside Templates are not visible outside of it.
Default elements are not implemented as UserControl, but as Custom Control.
In this case, there is a separate Sharp class with element logic.
And a separate default Template for this class.
In this implementation, since no XAML is used to create the element, the element does not have its own internal local visual tree.
And all its bindings (of the ElementName type) are interpreted in relation to the general visual tree.
To some extent, you partially implemented this in the Descriptor: ContentControl class.
Only instead of regregistering the default Template for the type, you set it up in the XAML App.
This implementation will work for an example, but in many cases it can create other problems.
Therefore, it is better to use the "standard" Custom Control for which the theme is created and the default Template is registered.
In the case of DescriptorTwo, you specify x: Class = "EmbedContent.UserControls.DescriptorTwo" and this automatically creates its own local visual tree, relative to which the ElementName bindings will work.
Slightly modified example for the solution you found.
using System.Windows;
using System.Windows.Controls;
namespace EmbedContent.CustomControls
{
public class Descriptor : ContentControl
{
#region Registering a default template
static Descriptor()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Descriptor), new FrameworkPropertyMetadata(typeof(Descriptor)));
}
#endregion
#region Ctor
public Descriptor() : base() { }
#endregion
#region Dependency-Properties
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(nameof(Text), typeof(string), typeof(Descriptor), new PropertyMetadata("Descriptor's default Text"));
#endregion
}
}
Theme with default templates - file Themes/Generic.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:customcontrols="clr-namespace:EmbedContent.CustomControls">
<Style TargetType="{x:Type customcontrols:Descriptor}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type customcontrols:Descriptor}">
<StackPanel>
<TextBlock x:Name="tblock" Text="Example Text in UserControl"/>
<TextBlock Text="{TemplateBinding Text}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
DescriptorTwo.xml:
<UserControl x:Class="EmbedContent.UserControls.DescriptorTwo"
x:Name="PART_Main"
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:EmbedContent.UserControls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<TextBlock x:Name="tblock" Text="Example Text in UserControl"/>
<TextBlock Text="{Binding Text, ElementName=PART_Main}"/>
</StackPanel>
</UserControl>
Examle Window:
<Window x:Class="EmbedContent.ExampleWindow"
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:EmbedContent"
xmlns:customcontrols="clr-namespace:EmbedContent.CustomControls"
xmlns:usercontrols="clr-namespace:EmbedContent.UserControls"
mc:Ignorable="d"
Title="ExampleWindow" Height="450" Width="800">
<StackPanel>
<TextBlock x:Name="tblock" Text="Example Text in Parent UIElement"/>
<Border Background="LightBlue" Margin="20" Padding="10"
BorderBrush="Blue" BorderThickness="2">
<customcontrols:Descriptor Text="{Binding Text, ElementName=tblock}"/>
</Border>
<Border Background="LightGreen" Margin="20" Padding="10"
BorderBrush="Green" BorderThickness="2">
<usercontrols:DescriptorTwo Text="{Binding Text, ElementName=tblock}"/>
</Border>
</StackPanel>
</Window>
Result:
As you can see in the XAML Designer and when launched at runtime, such bindings (in the UserControl) also work differently, which causes additional problems.
Therefore, the main purpose of the UserControl is to represent the Data coming through the DataContext.
Also consider the moment with the assignment of Content.
In UserControl in XAML, you set a value for this property.
And assigning it when used does not complement the presentation, but replace it.
In CustonControl, you can explicitly specify in the Template where to insert additional content.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:customcontrols="clr-namespace:EmbedContent.CustomControls">
<Style TargetType="{x:Type customcontrols:Descriptor}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type customcontrols:Descriptor}">
<StackPanel>
<TextBlock x:Name="tblock" Text="Example Text in UserControl"/>
<TextBlock Text="{TemplateBinding Text}"/>
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
<Window x:Class="EmbedContent.ExampleWindow"
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:EmbedContent"
xmlns:customcontrols="clr-namespace:EmbedContent.CustomControls"
xmlns:usercontrols="clr-namespace:EmbedContent.UserControls"
mc:Ignorable="d"
Title="ExampleWindow" Height="250" Width="400">
<StackPanel>
<TextBlock x:Name="tblock" Text="Example Text in Parent UIElement"/>
<Border Background="LightBlue" Margin="20" Padding="10"
BorderBrush="Blue" BorderThickness="2">
<customcontrols:Descriptor Text="{Binding Text, ElementName=tblock}">
<TextBlock Text="{Binding Text, ElementName=tblock}"/>
</customcontrols:Descriptor>
</Border>
<Border Background="LightGreen" Margin="20" Padding="10"
BorderBrush="Green" BorderThickness="2">
<usercontrols:DescriptorTwo Text="{Binding Text, ElementName=tblock}">
<TextBlock Text="{Binding Text, ElementName=tblock}"/>
</usercontrols:DescriptorTwo>
</Border>
</StackPanel>
</Window>
Result:

I finally found a solution but it still seems somewhat ugly so i expect someone to post a better solution.
I post the code here including an example highlighting the problem again.
Solution:
split UserControl / ContentControl up into a .cs class inheriting from e.g. ContentControl and set the Template describing the extra elements in the App.xaml Resources. Basically move the .xaml part as style into Resources.
Descriptor.cs
namespace EmbedContent.UserControls
{
class Descriptor : ContentControl{
#region Ctor
public Descriptor() : base() {}
#endregion
#region Dependency-Properties
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(Descriptor), new PropertyMetadata("Descriptor's default Text"));
#endregion
}
}
App.xaml
<Application
x:Class="EmbedContent.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:EmbedContent"
xmlns:uc="clr-namespace:EmbedContent.UserControls"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Style TargetType="uc:Descriptor">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="uc:Descriptor">
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="{Binding Path=Text, RelativeSource={RelativeSource AncestorType=uc:Descriptor}}" />
<ContentPresenter />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
The above can call and consume the Descriptor as i expect it from a WPF Control.
Here the Code for the UserControl, that breaks the binding when used:
DescriptorTwo.xaml
<UserControl
x:Class="EmbedContent.UserControls.DescriptorTwo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="_thisDescriptorTwo">
<UserControl.Template>
<ControlTemplate>
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="{Binding Path=Text, ElementName=_thisDescriptorTwo}" />
<ContentPresenter Content="{Binding Path=Content, ElementName=_thisDescriptorTwo}" />
</DockPanel>
</ControlTemplate>
</UserControl.Template>
DescriptorTwo.xaml.cs
namespace EmbedContent.UserControls
{
public partial class DescriptorTwo : UserControl
{
#region Ctor
public DescriptorTwo()
{
InitializeComponent();
}
#endregion
#region Dependency-Properties
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(DescriptorTwo), new PropertyMetadata("Descriptor's default Text"));
#endregion
}
}
And here an example of how both are called from another UserControl
Example.xaml
<UserControl
x:Class="EmbedContent.UserControls.EmbedContentExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:EmbedContent.UserControls"
x:Name="parent">
<StackPanel>
<Label>
<!-- Binding to parent works -->
<TextBlock Text="{Binding Path=MyText, ElementName=parent}" />
</Label>
<uc:Descriptor Text="description: ">
<!-- Binding to parent works -->
<TextBlock Text="{Binding Path=MyText, ElementName=parent}" />
</uc:Descriptor>
<uc:DescriptorTwo Text="description: ">
<!-- Binding to parent fails -->
<TextBlock Text="{Binding Path=MyText, ElementName=parent}" />
</uc:DescriptorTwo>
</StackPanel>
and the codebehind
Example.xaml.cs
namespace EmbedContent.UserControls
{
public partial class EmbedContentExample : UserControl
{
public EmbedContentExample()
{
InitializeComponent();
}
public string MyText
{
get { return (string)GetValue(MyTextProperty); }
set { SetValue(MyTextProperty, value); }
}
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register("MyText", typeof(string), typeof(EmbedContentExample), new PropertyMetadata("EmbedContentExample's MyText"));
}
}

Related

Is it possible to change properties on the validation error template in WPF?

I'm using the Validation.ErrorTemplate property in my XAML view to display an icon next to any invalid control.
For one particular control in my view, I need to set the icon in a slightly different position. Is it possible for me to use the same control template, but somehow update the Margin property for a certain control?
Here's my (abridged) code:
<UserControl>
<UserControl.Resources>
<ControlTemplate x:Key="ValidationTemplate" TargetType="Control">
<DockPanel>
<Grid
DockPanel.Dock="Right"
Height="16"
Margin="10,0,0,0"
VerticalAlignment="Center"
Width="16">
<Image
AutomationProperties.AutomationId="_validationIcon"
Source="{x:Static icons:Icons.ValidationIcon}"
ToolTip="{Binding Path=ErrorContent}" />
</Grid>
<AdornedElementPlaceholder />
</DockPanel>
</ControlTemplate>
<ItemsControl Validation.ErrorTemplate="{StaticResource ValidationTemplate}" />
</UserControl>
The only way I've managed to achieve what I need is to create a new ControlTemplate just for the control that requires a different icon placement. I would rather reuse my original control template if possible.
I had exactly same requirement in my old project. I solved it using an attached dependency property:
public static class ErrorTemplateProperties
{
public static readonly DependencyProperty ErrorMarginProperty = DependencyProperty.RegisterAttached
(
"ErrorMargin",
typeof(Thickness),
typeof(ErrorTemplateProperties),
new FrameworkPropertyMetadata(new Thickness(10,0,0,0))
);
public static Thickness GetErrorMargin(DependencyObject obj)
{
return (Thickness)obj.GetValue(ErrorMarginProperty);
}
public static void SetErrorMargin(DependencyObject obj, Thickness value)
{
obj.SetValue(ErrorMarginProperty, value);
}
}
add it to ValidationTemplate:
<ControlTemplate x:Key="ValidationTemplate" TargetType="Control">
<DockPanel>
<Grid
DockPanel.Dock="Right"
Margin="{Binding Path=AdornedElement.(local:ErrorTemplateProperties.ErrorMargin), ElementName=ui}"
Height="16"
VerticalAlignment="Center"
Width="16">
<Image
AutomationProperties.AutomationId="_validationIcon"
Source="{x:Static icons:Icons.ValidationIcon}"
ToolTip="{Binding Path=ErrorContent}" />
</Grid>
<AdornedElementPlaceholder x:Name="ui"/>
</DockPanel>
</ControlTemplate>
and then optionally change on required ui elements:
<ItemsControl helpers:ErrorTemplateProperties.ErrorMargin="0,0,0,0"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}" />
I also found a workaround with DynamicResources but attached DP are more flexible and laconic in my opinion

In a WPF control that acts as a container, how do I place the content?

I am writing a WPF control that is meant to be a container in the same way Border and ScrollViewer are containers. It is called EllipsisButtonControl, and it is supposed to place an ellipsis button to the right of its content. Here's an example of how I intend for it to be used:
<local:EllipsisButtonControl>
<TextBlock Text="Testing" />
</local:EllipsisButtonControl>
Here is the XAML for EllipsisButtonControl:
<ContentControl
x:Class="WpfApplication1.EllipsisButtonControl"
x:Name="ContentControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0" Content="{Binding ElementName=ContentControl, Path=Content}" />
<Button Grid.Column="1" Command="{Binding ElementName=ContentControl, Path=Command}" Margin="3,0" Width="30" Height="24" MaxHeight="24" VerticalAlignment="Stretch" Content="..." />
</Grid>
</ContentControl>
And here is the code behind:
using System.Windows;
using System.Windows.Input;
namespace WpfApplication1
{
public partial class EllipsisButtonControl
{
public EllipsisButtonControl()
{
InitializeComponent();
}
public static string GetCommand(DependencyObject obj)
{
return (string)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, string value)
{
obj.SetValue(CommandProperty, value);
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached(
name: "Command",
propertyType: typeof(ICommand),
ownerType: typeof(EllipsisButtonControl),
defaultMetadata: new UIPropertyMetadata());
}
}
This doesn't work. It crashes the Designer with a System.Runtime.Remoting.RemotingException.
I believe the binding on the ContentPresenter of the EllipsisButtonControl XAML is wrong, but I don't know how to make it right. What is the appropriate syntax to make that line reference the control's content? (e.g. The TextBlock defined in the usage example)
Edit:
poke provided a comprehensive answer below (including working code), but for the benefit of others who might share my initial misunderstanding, let me summarize the key concept here: A container control cannot "place content", per se. It achieves the desired effect by defining a template that modifies the way the calling XAML presents the content. The rest of the solution follows from that premise.
<local:EllipsisButtonControl>
<TextBlock Text="Testing" />
</local:EllipsisButtonControl>
This does set the Content of your user control. But so does the following in the user control’s XAML:
<ContentControl …>
<Grid>
…
</Grid>
</ContentControl>
The calling XAML has precendence here, so whatever you do inside that user control’s XAML is actually ignored.
The solution here is to set the template of the user control. The template, in this case the control’s control template, determines how the control itself is rendered. The simplest template for a user control (and also its default) is just using a ContentPresenter there, but of course, you want to add some stuff around that, so we have to overwrite the template. This generally looks like this:
<ContentControl …>
<!-- We are setting the `Template` property -->
<ContentControl.Template>
<!-- The template value is of type `ControlTemplate` and we should
also set the target type properly so binding paths can be resolved -->
<ControlTemplate>
<!-- This is where your control code actually goes -->
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
Now this is the frame you need to make this work. However, once you’re inside the control template, you need to use the proper binding type. Since we are writing a template and want to bind to properties of the parent control, we need to specify the parent control as the relative source in bindings. But the easiest way to do that is to just use the TemplateBinding markup extension. Using that, a ContentPresenter can be placed like this inside the ControlTemplate above:
<ContentPresenter Content="{TemplateBinding Content}" />
And that should be all you need here in order to get the content presenter working.
However, now that you use a control template, of course you need to adjust your other bindings too. In particular the binding to your custom dependency property Command. This would generally look just the same as the template binding to Content but since our control template is targetting the type ContentControl and a ContentControl does not have your custom property, we need to explicitly reference your custom dependency property here:
<Button Command="{TemplateBinding local:EllipsisButtonControl.Command}" … />
Once we have that, all the bindings should work fine. (In case you are wondering now: Yes, the binding always targets the static dependency property on the type)
So, to sum this all up, your custom content control should look something like this:
<ContentControl
x:Class="WpfApplication1.EllipsisButtonControl"
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:WpfApplication1"
d:DesignHeight="30" d:DesignWidth="300" mc:Ignorable="d">
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<Grid>
<ContentPresenter Grid.Column="0"
Content="{TemplateBinding Content}" />
<Button Grid.Column="1" Content="…"
Command="{TemplateBinding local:EllipsisButtonControl.Command}" />
</Grid>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
Try replacing this line:
<ContentPresenter Grid.Column="0" Content="{Binding ElementName=ContentControl, Path=Content}" />
With this
<ContentPresenter Grid.Column="0" Content={Binding Content} />
In your existing code you are making this ContentPresenter display the generated content of EllipsesButtonControl, which includes the ContentPresenter which must render the generated content of ElipsesButtonControl which includes the ContentPresenter..... Unlimited recursion.
The XAML of your EllipsisButtonControl already sets its Content to the top-level Grid. What you probably wanted is to create a ControlTemplate, e.g. like this:
<ContentControl x:Class="WpfApplication1.EllipsisButtonControl"
x:Name="ContentControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignHeight="30" d:DesignWidth="300">
<ContentControl.Template>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"
Content="{Binding ElementName=ContentControl, Path=Content}"/>
<Button Grid.Column="1"
Command="{Binding ElementName=ContentControl, Path=Command}"
Margin="3,0" Width="30" Height="24" MaxHeight="24"
VerticalAlignment="Stretch" Content="..." />
</Grid>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>

WPF C# Commands bound to userControl won't fire

So, I have created a UserControl which really is an "advanced button".
I have implemented Dependency Properties, one of which is ICommand, that is supposed to be bindable further when control is used in an actual Window.
However, for some reason the Command doesn't work.
When I tried an exact same approach on a regular button, everything worked fine (thus it's not the fault of my DelegateCommand implementation or my ViewModel).
I tried to followup on why the bound command doesn't fire, but I couldn't find a reliable reason for it not to.
Here is my UserControl XAML:
<Window x:Class="NoContact.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:NoContact"
mc:Ignorable="d"
xmlns:userControls="clr-namespace:NoContact.UserControls"
Title="MainWindow" Height="800" Width="960" Background="#22282a">
<Border BorderThickness="1" BorderBrush="#ffcd22" Margin="10,10,10,10">
<Grid>
<Button HorizontalContentAlignment="Stretch" Foreground="{Binding ElementName=ImageButtonUC, Path=Foreground}"
Background="{Binding ElementName=ImageButtonUC, Path=Background}">
<DockPanel Width="{Binding ElementName=ImageButtonUC, Path=ActualWidth}">
<Image Source="{Binding ElementName=ImageButtonUC, Path=Image}" DockPanel.Dock="Left"
Height="{Binding ElementName=ImageButtonUC, Path=ActualHeight, Converter={StaticResource HeightConverter}}"
Width="{Binding ElementName=ImageButtonUC, Path=ActualWidth, Converter={StaticResource HeightConverter}}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding ElementName=ImageButtonUC, Path=Text}" FontSize="17" VerticalAlignment="Center" />
</DockPanel>
</Button>
</Grid>
<UserControl.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
And here is my UserControl code-behind:
public partial class ImageButton : UserControl
{
// OTHER IRRELEVANT CLASS PARAMETERS ARE HERE
public ICommand ClickCommand
{
get { return (ICommand)GetValue(ClickCommandProperty); }
set { SetValue(ClickCommandProperty, value); }
}
// OTHER IRRELEVANT DEPENDENCY PROPERTIES ARE HERE
public static DependencyProperty ClickCommandProperty =
DependencyProperty.Register("ClickCommand", typeof(ICommand), typeof(ImageButton));
public ImageButton()
{
InitializeComponent();
}
}
Finally, this is how I use my control:
<userControls:ImageButton x:Name="phoneButton" ClickCommand="{Binding Path=MyButtonClickCommand}" Style="{StaticResource phoneImageButtonUCStyle}" Text="Telefon" Width="200" Height="100" VerticalAlignment="Top" />
The DataContext of the control is set on a stackpanel, that is wrapped around my control. I have also tried setting it directly on the control, no effect.
Again, doing the same on a regular button works just fine.
I have finally solved the problem - and it was pretty trivial.
I had ommited the most important binding - UserControl's button Command to UserControl's Dependency property.
Changing it like this made it work:
<Button HorizontalContentAlignment="Stretch" Foreground="{Binding ElementName=ImageButtonUC, Path=Foreground}"
Background="{Binding ElementName=ImageButtonUC, Path=Background}"
Command="{Binding ElementName=ImageButtonUC, Path=ClickCommand}">

WPF TabControl with ContentControl

I have searched a lot on SO and didn't find answer to my problems.
I want to use TabControl with MVVM. Here is how I add TabControl to MainWindow.xaml
<Window.Resources>
<DataTemplate DataType="{x:Type models:PartnersViewModel}">
<views:PartnersView />
</DataTemplate>
<DataTemplate DataType="{x:Type models:ProjectsViewModel}">
<views:ProjectsView />
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<models:ApplicationViewModel/>
</Window.DataContext>
<TabControl ItemsSource="{Binding PageViewModels}" TabStripPlacement="Left">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
PageViewModels is ObservableCollection<IPageViewModel>.
IPageViewModel is simple interface with one property Name. There are 2 implementation of this interface PartnersViewModel and ProjectsViewModel.
public class ProjectsViewModel : IPageViewModel
{
public String Name
{
get { return "Projects"; }
}
}
public class PartnersViewModel : IPageViewModel
{
public String Name
{
get { return "Partners"; }
}
}
I want each tab to be displayed as ContentControl. Header text is taken from Name property. My ProjectsView and PartnersView looks like this:
<UserControl x:Class="WANIRPartners.Views.ProjectsView"
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" >
<Grid>
<Label Content="Projects Content" />
</Grid>
</UserControl>
Using this code, header and content in TabControl is exactly the same. 'Projects Content'/'Partners Content' are shown in tab headers and also(this is ok) in tab content. When I change <Label/> to <DataGrid/> tab headers contains datagrid (sic!).
How can I get this working properly. I mean how can I display headers as value of property Name and tab content as properly rendered <views:PartnersView /> or <views:ProjectsView /> depending on what is in PageViewModels.
your code should work, dont have a IDE atm. you can also use Snoop to check your bindings at runtime. i changed the ContentTemplate to:
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding .}" />
</DataTemplate>
</TabControl.ContentTemplate>
and it works. but even your code works in my testapp.
I have discovered solution for Elysium. Adding fter adding code belowe everything work fine.
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}" BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="Header" Value="{Binding Name}"/>
</Style>
</TabControl.Resources>

Why are my UserControl bindings not working?

I thought this would be pretty simple to do but seems I must be missing something blinding obvious.
The problem is that I am passing values to my UserControl (BoxPanel) but the values are not displayed. The blue box is displayed without text.
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<l:BoxPanel Number="1" Text="Hi" />
</Grid>
</Window>
BoxPanel.xaml
<UserControl x:Class="WpfApplication1.BoxPanel"
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"
Height="50" Width="90">
<Border Background="Blue">
<StackPanel>
<TextBlock FontSize="20" HorizontalAlignment="Center"
Text="{Binding Number}" />
<Label FontSize="10" HorizontalAlignment="Center" Foreground="White"
Content="{Binding Text}" />
</StackPanel>
</Border>
BoxPanel.xaml.xs
public partial class BoxPanel : UserControl
{
public static readonly DependencyProperty NumberProperty =
DependencyProperty.Register("Number", typeof(decimal), typeof(BoxPanel));
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(BoxPanel));
public BoxPanel()
{
InitializeComponent();
}
public decimal Number
{
get { return (decimal)GetValue(NumberProperty); }
set { SetValue(NumberProperty, value); }
}
public string Text
{
get { return (string)base.GetValue(TextProperty); }
set { base.SetValue(TextProperty, value); }
}
}
Binding paths, by default, are rooted at the DataContext. But you wish to bind to properties defined on the UserControl. So you have to redirect them somehow. I usually just do it by ElementName.
<UserControl x:Class="WpfApplication1.BoxPanel"
x:Name="BoxPanelRoot"
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"
Height="50" Width="90">
<Border Background="Blue">
<StackPanel>
<TextBlock Text="{Binding Number, ElementName=BoxPanelRoot}" />
<Label Content="{Binding Text, ElementName=BoxPanelRoot}" />
</StackPanel>
</Border>
It seems a little odd at first, and somewhat annoying to redirect bindings like this, but it is preferrable than other methods which utilize the DataContext within the UserControl. If you block the DataContext by, say, setting it to the root of the UserControl, you have effectively blocked the best method of passing data into the UserControl.
Rule of thumb, when binding in a UserControl, leave the DataContext alone unless you are explicitly binding against data passed to the UserControl.

Categories