Set WPF UserControl properties to a Control inside it [duplicate] - c#

I know how to create a custom user control in WPF but how can I make it so that someone can provide an ItemTemplate?
I have a user control that is a mixture of several other WPF controls, one of them being a ListBox. I'd like to let the user of the control specify the content of the list box but I'm not sure how to pass that information through.
EDIT: The accepted answer works with the following correction:
<UserControl x:Class="WpfApplication6.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:WpfApplication6">
<ListBox ItemTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type src:MyControl}}, Path=ItemsSource}" />
</UserControl>

You will want to add a DependencyProperty to your control. The xaml will look slightly different if you are deriving from UserControl or Control.
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
}
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(MyControl), new UIPropertyMetadata(null));
public DataTemplate ItemTemplate
{
get { return (DataTemplate) GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
}
Here is xaml for a UserControl.
<UserControl x:Class="WpfApplication6.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:WpfApplication6">
<ListBox ItemTemplate="{Binding ItemTemplate, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type src:MyControl}}}" />
</UserControl>
Here is xaml for a Control:
<Style TargetType="{x:Type src:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type src:MyControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ListBox ItemTemplate="{TemplateBinding ItemTemplate}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Related

Bind to Application.ActualTheme in a ControlTemplate XAML UWP

I'm trying to create a control that uses a RevealBorderBrush as its border brush in XAML. I want to use the correct TargetTheme value for the brush, so I'm trying to bind to my application's current ActualTheme value. I'm using a templated control to do this. My C# code behind file is just the empty constructor that inherits from Control and sets the default style key. The following is my Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Plank">
<Style TargetType="local:PlankPL" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:PlankPL">
<Border
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}">
<Border.BorderBrush>
<RevealBorderBrush TargetTheme="{Binding Source=local:App, Path=ActualTheme}"/>
</Border.BorderBrush>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
I'm pretty sure the binding statement is incorrect, but I'm not sure how to write it.
I haven't tested this but it should work. Name your control then use the below code, replace ControlTemplateName with what you used.
You can’t bind background data in ResourceDictionary, it doesn’t work.
It is recommended to write a UserControl and bind the RevealBorderBrush property of Border like following.
MyUserControl1.xaml:
<UserControl..>
<StackPanel>
<Border BorderThickness="3">
<Border.BorderBrush>
<RevealBorderBrush TargetTheme="{x:Bind theme1}"/>
</Border.BorderBrush>
</Border>
<TextBox Text = "text demo text "/>
</StackPanel>
</UserControl>
MyUserControl1.xaml.cs:
public sealed partial class MyUserControl1 : UserControl
{
private ApplicationTheme theme1;
public MyUserControl1()
{
this.InitializeComponent();
theme1 = Application.Current.RequestedTheme;
}
}

Setting a style to wpf custom control overrides everything

I am creating a split button where I am trying to set a default template to it so if the split button is to be used elsewhere outside of the control, it can be. The issue here is, is that when a user calls the split button into their control and they attach their style to it, it completely removes everything from the split button. I'm not entirely sure how to fix it. I would appreciate any help.
MySplitButton.xaml:
<local:SplitButton x:Class="WpfApp4.SplitButton.MySplitButton"
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:WpfApp4.SplitButton"
mc:Ignorable="d"
d:DesignHeight="25" d:DesignWidth="100">
<local:SplitButton.Resources>
</local:SplitButton.Resources>
<local:SplitButton.Style>
<Style TargetType="{x:Type local:SplitButton}" BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SplitButton}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="25"/>
</Grid.ColumnDefinitions>
<local:LockableToggleButton Grid.Column="0">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True"/>
</local:LockableToggleButton>
<Button Grid.Column="1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</local:SplitButton.Style>
</local:SplitButton>
MySplitButton.xaml.cs
public partial class MySplitButton : SplitButton
{
public MySplitButton()
{
InitializeComponent();
}
}
public class SplitButton : ToggleButton
{
public ICommand PrimaryButtonCommand
{
get { return (ICommand)GetValue(PrimaryButtonCommandProperty); }
set { SetValue(PrimaryButtonCommandProperty, value); }
}
public static readonly DependencyProperty PrimaryButtonCommandProperty;
public bool ToggleLock
{
get { return (bool)GetValue(ToggleLockProperty); }
set { SetValue(ToggleLockProperty, value); }
}
public static readonly DependencyProperty ToggleLockProperty;
public bool ContextMenuOpen
{
get { return (bool)GetValue(ContextMenuOpenProperty); }
set { SetValue(ContextMenuOpenProperty, value); }
}
public static readonly DependencyProperty ContextMenuOpenProperty;
static SplitButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SplitButton), new FrameworkPropertyMetadata(typeof(SplitButton)));
PrimaryButtonCommandProperty = DependencyProperty.Register("PrimaryButtonCommand", typeof(ICommand), typeof(SplitButton), new FrameworkPropertyMetadata(null));
ToggleLockProperty = DependencyProperty.Register("ToggleLock", typeof(bool), typeof(SplitButton), new UIPropertyMetadata(false));
ContextMenuOpenProperty = DependencyProperty.Register("ContextMenuOpen", typeof(bool), typeof(SplitButton), new FrameworkPropertyMetadata(false));
}
}
public class LockableToggleButton : ToggleButton
{
public bool ToggleLock
{
get { return (bool)GetValue(ToggleLockProperty); }
set { SetValue(ToggleLockProperty, value); }
}
public static readonly DependencyProperty ToggleLockProperty =
DependencyProperty.Register("ToggleLock", typeof(bool), typeof(LockableToggleButton), new UIPropertyMetadata(false));
protected override void OnToggle()
{
if (!ToggleLock)
{
base.OnToggle();
}
}
}
So when I call MySplitButton on MainWindow like this, and attach a style to it, everything gets overridden and I don't know what I am doing wrong:
<Window x:Class="WpfApp4.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:WpfApp4"
xmlns:cc="clr-namespace:WpfApp4.SplitButton"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.CommandBindings>
<CommandBinding Command="{x:Static local:MainWindow.PrimaryButtonUICommand}" Executed="CommandBinding_Executed"/>
</Window.CommandBindings>
<Window.Resources>
<Style x:Key="thisStyle" TargetType="{x:Type cc:SplitButton}">
<Setter Property="Background" Value="Yellow"/>
</Style>
</Window.Resources>
<Grid>
<cc:MySplitButton x:Name="SplitButton" Margin="346,197,345,188" Style="{DynamicResource thisStyle}">
<StackPanel Orientation="Horizontal">
<Label Content="hello"/>
</StackPanel>
</cc:MySplitButton>
</Grid>
</Window>
The way that you created the XAML markup for your custom button, the style will get instantiated and applied to a MySplitButton instance when it is created. Specifying a TargetType on a style does not automatically inherit the default style. You can base a style on another using the BasedOn attribute. However, you cannot reference your default style, since it is only available on an instance of MySplitButton. The solution is to extract the default style to a resource dictionary to share it.
Usually, when creating custom controls, you would create a dedicated assembly and create a resource dictionary called Generic.xaml in a Themes folder. This resource dictionary contains your default control styles. Note that the TargetType is MySplitButton, because that is your custom control, not SplitButton. Since there is no x:Key, this style is implicit and will be applied to all MySplitButton controls in scope automatically.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyControlLibrary">
<!-- Default style for your split button -->
<Style TargetType="{x:Type local:MySplitButton}" BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MySplitButton}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="25"/>
</Grid.ColumnDefinitions>
<local:LockableToggleButton Grid.Column="0">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True"/>
</local:LockableToggleButton>
<Button Grid.Column="1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ...styles, templates and resources for other controls. -->
</ResourceDictionary>
In other projects you have to include the resources in the application resources, or at least in any resource dictionary in a scope where you use the controls. Otherwise, the style cannot be resolved.
<Application x:Class="MyApplication"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MyControlLibrary;component/Themes/Generic.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Specifying a TargetType does not automatically inherit a style of a control.
Gets or sets the type for which this style is intended.
In order to base one style on another, you have to specify the base style through the BasedOn attribute.
Gets or sets a defined style that is the basis of the current style. [...] When you use this property, the new style will inherit the values of the original style that are not explicitly redefined in the new style.
Consequently, you have to adapt your new thisStyle like below.
<Style x:Key="thisStyle" TargetType="{x:Type cc:MySplitButton}" BasedOn="{StaticResource {x:Type cc:MySplitButton}}">
<Setter Property="Background" Value="Yellow"/>
</Style>
Remember, your original SplitButton style must be available in the current scope, so your users must make sure to include the corresponding resource dictionary in their library or application.
Try to use BasedOn.
<Window.Resources>
<Style x:Key="thisStyle" TargetType="{x:Type cc:SplitButton}" BasedOn="{StaticResource {x:Type cc:SplitButton}}">
<Setter Property="Background" Value="Yellow"/>
</Style>
</Window.Resources>

How to hide tab control when item source list is empty

I have a WPF .NET Core application with a TabControl bound to an ObservableCollection for the TabItems. I would like the TabControl to be hidden when the ObservableCollection becomes empty, and I would like to display another panel in its place. Then, when an item is added back to the ObservableCollection, I want the alternate panel hidden and the TabControl reshown. How would I accomplish this, hopefully in XAML with as little code-behind as possible? I know I can do it in code-behind.
Below is the key section of the app. I have hidden the TabControl, and included a Border control to represent the Panel that I will show when the TabControl is hidden.
<Window x:Class="TabTest.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:TabTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Border Name="emptyTabPanel" Grid.Row="1" BorderBrush="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}"
BorderThickness="1,1,1,1" Margin="5,0,5,5" Visibility="Hidden">
</Border>
<TabControl Name="MainTabControl" Visibility="Visible">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Header}" MinWidth="60"/>
<Button BorderThickness="0" Background="Transparent" Height="16" Width="15" Margin="15,2,0,0">
<Image Source="images/close.png" Height="8"/>
</Button>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
</Window>
using System.Windows;
using System.Collections.ObjectModel;
namespace TabTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<TabEntry> tabEntries;
public MainWindow()
{
InitializeComponent();
tabEntries = new ObservableCollection<TabEntry>();
MainTabControl.ItemsSource = tabEntries;
for (int i = 1; i <= 5; i++)
{
tabEntries.Add(new TabEntry { Header = "tab " + i });
}
}
}
public class TabEntry
{
public string Header { get; set; }
}
}
All ItemsControls provide a HasItems property that you can use in a Trigger. In contrast to a DataTrigger on ItemsSource.Count this also works when ItemsSource is not set at all.
<TabControl ...>
<TabControl.Style>
<Style TargetType="TabControl">
<Style.Triggers>
<Trigger Property="HasItems" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</TabControl.Style>
...
</TabControl>
You can do it with a DataTriger in a Style. Note that you need to remove Visibility="Visible" or the Setter won't be able to change it.
<TabControl Name="MainTabControl" Background="Red">
<TabControl.Style>
<Style TargetType="TabControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ItemsSource.Count, RelativeSource={RelativeSource Self}}" Value="0">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
<TabControl.ItemTemplate>
<!-- and so on -->
You need a proper view model for this, rather than binding the tabs directly to the collection. That view model would include a HasItems property which you'll bind your TabControl visibility to, and an inverse property - say IsEmpty - which you'll bind the panel's visibility to.
Bind ObservableCollection's events to listen to changes in item count and raise PropertyChanged events for your view model appropriately.

Attached property always has a default value in a style

I'm working with WPF and I want to use attached properties to work with some styling things in the validation of the controls (my example of the problem is really simple, binding a simple text).
This is my attached property:
public class ToolTipExtension
{
public static readonly DependencyProperty ShowToolTipProperty = DependencyProperty.RegisterAttached(
"ShowToolTip", typeof(string), typeof(ToolTipExtension), new PropertyMetadata("Deffault"));
public static void SetShowToolTip(DependencyObject element, string value)
{
element.SetValue(ShowToolTipProperty, value);
}
public static string GetShowToolTip(DependencyObject element)
{
return (string) element.GetValue(ShowToolTipProperty);
}
}
I have a simple style dictionary like this
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:at="clr-namespace:CarpetaTecnicaWPF.AttachedProperties"
>
<Style TargetType="{x:Type TextBox}" x:Key="Blah" >
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Grid>
<TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(at:ToolTipExtension.ShowToolTip)}" FontSize="50"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
As you can see, I'm trying to bind the Text property to my attached property.
In my Page, I'm using the style like this:
<TextBox Style="{StaticResource Blah}" at:ToolTipExtension.ShowToolTip="Prueba?"/>
The thing is, the value Prueba? does not appear. When I inspect the tree, I see this:
But in runtime, the result of the binding is Deffault
What am I doing wrong?
Your binding is incorrect.
The TemplatedParent in this case is not what you actually need. The ControlTemplate for the error is not applied to the text box itself, it's a stand-alone control template. So you are just getting a default value from a wrong FrameworkElement.
To access the text box your error template is applied to, you need to use the AdornedElementPlaceholder in your ControlTemplate. From that AdornedElementPlaceholder, you can access your text box via the AdornedElement property.
Here is an example:
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Grid>
<AdornedElementPlaceholder x:Name="adorner"/>
<TextBlock Text="{Binding ElementName=adorner, Path=AdornedElement.(at:ToolTipExtension.ShowToolTip)}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>

ContentControl.Template is null even it is in XAML

Maybe title is confusing you, but the problem is concretely here : I have XAML file which there is ControlTemplate for MyType (inherits from ContentControl). And in that Template i set its DataContext like this :
DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
And when i want to get DataContext from MyType it shows null for first time. After clicking on it ( it is rectangle in window ) it changes to object what i need. I searched all things, all events what i am doing in LeftButtonDown there is no ApplyTemplate(),UpdateLayout() methods. Even i call these methods it doesn't help. What need to do ? P.S this DataContext is Parent of this item, i can send is a parameter but there must be other solution
this is part of XAML which Template located:
<Style TargetType="{x:Type s:Connector}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type s:Connector}">
<Grid Name="grid" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<Border BorderBrush="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
Path=BorderBrush}" x:Name="border" BorderThickness="2" Background="Transparent">
<Image Source="/DiagramDesigner;component/Resources/1337238611_port.png"/>
</Border>
<Rectangle Fill="Transparent" Margin="-2"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And this is where i want to get DataContext:
public DesignerItem ParentDesignerItem //
{
get
{
if (parentDesignerItem == null)
{
parentDesignerItem = this.DataContext as DesignerItem;
// if (parentDesignerItem==null) parentDesignerItem = (this.Template.FindName("grid", this) as Grid).DataContext as DesignerItem;
}
return parentDesignerItem;
}
set
{
parentDesignerItem = value;
}
}
I must say that this Style is in XAML which Style of the DesignerItem also.
The problem is in my algorithm P.S i restore some information from SQL, and add 2 times more Connectors with null parameters and when i want to access it gives me these null objects.

Categories

Resources