I want to create a custom control that acts like a ToolBar control but contains pre-built elements in addition to those provided by the user.
As an example, with the code below I need a tool bar that contains predefined elements then the two user provided buttons:
<MyToolBar>
<Button>User button 1</Button>
<Button>User button 2</Button>
</MyToolBar>
Which would then be equivalent to:
<ToolBar>
<Button>Predefined button 1</Button>
<Button>Predefined button 2</Button>
<Button>User button 1</Button>
<Button>User button 2</Button>
</ToolBar>
I have tried inheriting ToolBar class and specify a CompositeCollection as ToolBar.ItemSource, however it fails, throwing
Cannot find governing FrameworkElement or FrameworkContentElement for target element
<!-- Wrong Code -->
<Style TargetType="{x:Type local:MyToolBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyToolBar}">
<ToolBar>
<ToolBar.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding ItemsSource,RelativeSource={RelativeSource TemplatedParent}}" />
<Button>Predefined button 1</Button>
<Button>Predefined button 2</Button>
</CompositeCollection>
</ToolBar.ItemsSource>
</ToolBar>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Define a read-only collection-type dependency property and add the default items to it in your class:
[ContentProperty(nameof(ItemsSource))]
public class MyToolBar : Control
{
private static readonly DependencyPropertyKey s_itemsSourcePropertyKey =
DependencyProperty.RegisterReadOnly(
name: nameof(ItemsSource),
propertyType: typeof(List<FrameworkElement>),
ownerType: typeof(MyToolBar),
typeMetadata: new FrameworkPropertyMetadata()
);
public MyToolBar()
{
SetValue(s_itemsSourcePropertyKey, new List<FrameworkElement>()
{
new Button(){ Content = "Predefined button 1" },
new Button(){ Content = "Predefined button 2" }
});
}
public List<FrameworkElement> ItemsSource =>
(List<FrameworkElement>)GetValue(s_itemsSourcePropertyKey.DependencyProperty);
}
Template:
<Style TargetType="{x:Type local:MyToolBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyToolBar}">
<ToolBar ItemsSource="{Binding ItemsSource,
RelativeSource={RelativeSource AncestorType=local:MyToolBar}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Usage:
<local:MyToolBar>
<Button>User button 1</Button>
<Button>User button 2</Button>
</local:MyToolBar>
Result:
Based on this question, it appear that the binding on CollectionContainer.Collection do not update the container content when the source content change.
You have to use a CollectionViewSource has a proxy:
<Style x:Key="MyToolBarStyle" TargetType="{x:Type ToolBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToolBar}">
<ToolBar>
<ToolBar.Resources>
<CollectionViewSource x:Key="TemplateItems" Source="{TemplateBinding ItemsSource}" />
</ToolBar.Resources>
<ToolBar.ItemsSource>
<CompositeCollection>
<Button>Predefined button 1</Button>
<Button>Predefined button 2</Button>
<CollectionContainer Collection="{Binding Source={StaticResource TemplateItems}}" />
</CompositeCollection>
</ToolBar.ItemsSource>
</ToolBar>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Related
I am porting a Bootstrap theme to WPF and faced a problem: I can't change some properties using additional style for StackPanel. I have ResourceDictionary containing BtnBase base style and BtnPrimary, BtnSecondary etc. All of them were inherited from BtnBase and contain only color properties. BtnBase does not contain any margin rule. When I try to add margin rule for a current StackPanel, there is no effect. I read about styles and knew that more than one style can't be applied, styles in ResourceDictionary have higher priority and I should use BasedOn. But I need to copy and paste my style in the StackPanel and replace BasedOn for each button variant. Is there a way to bypass it and add margin property for all styles?
code:
<StackPanel Orientation="Horizontal" Height="32">
<StackPanel.Resources>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource BtnBase}>
<Setter Property="Margin" Value="0,0,10,0"/>
</Style>
</StackPanel.Resources>
<Button Style="{DynamicResource BtnPrimary}" Content="Primary" />
<Button Style="{DynamicResource BtnSecondary}" Content="Secondary" />
<Button Style="{DynamicResource BtnSuccess}" Content="Success" />
<Button Style="{DynamicResource BtnInfo}" Content="Info" />
<Button Style="{DynamicResource BtnWarning}" Content="Warning" />
<Button Style="{DynamicResource BtnDanger}" Content="Danger" />
<Button Style="{DynamicResource BtnLight}" Content="Light" />
<Button Style="{DynamicResource BtnDark}" Content="Dark" />
</StackPanel>
You can treat all buttons as Items in ListBox.
ListBox will create a ListBoxItem for each Button, and you can add Margin to those ListBoxItems, using ItemContainerStyle. That doesn't modify Buttons styles, but creates same effect.
In ItemContainerStyle I also change ListBoxItem template to undo hover, selection colors, etc, which can change appearance - just plain ContentPresenter is left.
ItemsPanel by default is vertical StackPanel, but I changed ItemsPanel and layout to horizontal StackPanel.
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Height="32"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Margin" Value="4"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<Button Style="{DynamicResource BtnPrimary}" Content="Primary" />
<Button Style="{DynamicResource BtnSecondary}" Content="Secondary" />
<Button Style="{DynamicResource BtnSuccess}" Content="Success" />
<Button Style="{DynamicResource BtnInfo}" Content="Info" />
<Button Style="{DynamicResource BtnWarning}" Content="Warning" />
<Button Style="{DynamicResource BtnDanger}" Content="Danger" />
<Button Style="{DynamicResource BtnLight}" Content="Light" />
<Button Style="{DynamicResource BtnDark}" Content="Dark" />
</ListBox>
I have a custom contextmenu:
<Window.Resources>
<ContextMenu x:Key="RowMenu" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<ContextMenu.Style>
<Style TargetType="ContextMenu">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Background="Transparent">
<Border Background="#1c1c1c" Height="70" Width="170" CornerRadius="10">
<StackPanel Orientation="Vertical">
<Button x:Name="openinBrowser" Click="Button_Click_1">
<Grid Width="170">
<materialDesign:PackIcon Kind="OpenInApp" VerticalAlignment="Center" Foreground="{StaticResource PrimaryHueMidBrush}" HorizontalAlignment="Left"/>
<Label FontFamily="Champagne & Limousines" Content="Action 1" FontSize="7" HorizontalAlignment="Center" Foreground="LightGray" VerticalAlignment="Center"/>
</Grid>
<Button.Style>
<Style BasedOn="{StaticResource MaterialDesignRaisedAccentButton}" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{StaticResource PrimaryHueMidBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
</Button.Style>
</Button>
</StackPanel>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ContextMenu.Style>
</ContextMenu>
</Window.Resources>
How would I be able to add a name to the Button so I can enable and disable it in my c# (without using binding), I have tried putting x:Name="" but it doesn't work, but if I add a button click it works? I am quite confused, any help would be appreciated!
I still say you should be doing this properly with data-binding, but if you insist...there are a couple of different ways to go about this.
Context menus aren't part of the regular visual tree, so you have to access them directly. Give your context menu a name, and then find the button by traversing its template's visual tree:
// button has to be templated in order for this to work,
// so don't try it in the parent window's constructor
// (add a this.contextMenu.Loaded handler instead if you have to)
var button = this.contextMenu.Template.FindName("openinBrowser", this.contextMenu) as Button;
If your visual tree is particularly complex then a faster option would be to create a boolean resource in your window's resources block:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
<Window.Resources>
<sys:Boolean x:Key="ButtonEnabled">True</sys:Boolean>
</Window.Resources>
...and then bind your button to that dynamically:
<Button x:Name="openinBrowser" IsEnabled="{DynamicResource ButtonEnabled}">
This breaks your "no binding" rule though, which is why I was asking why you're so adamant about not using data-binding...you can still use it, even if you're not binding to the data context. In this scenario you set the value of that resource in your code instead:
this.Resources["ButtonEnabled"] = false;
I have my PDFDocument bound to the FlowDocumentScrollViewer.
<FlowDocumentScrollViewer
Document="{Binding Path=PDFDocument}"
/>
How can I add a new context menu item to a text box inside of the viewing area
Eventually I found how to do it
You can attach the context menu to each of TextBox Elements using a style property setter like this:
<Window.Resources>
<ContextMenu x:Key="contextMenu" >
<MenuItem Name="mnuOpen" Header="_Open Link" Command="{Binding TextBoxContextMenuCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}"/>
<MenuItem Name="mnuView" Header="_View Properties" Command="{Binding TextBoxContextMenuCommand}"/>
</ContextMenu>
<Style TargetType="TextBox">
<Setter Property="ContextMenu" Value="{DynamicResource contextMenu}" />
</Style>
</Window.Resources>
I have a user control which I'd like to be used like so:
// MainPage.xaml
<my:MyControl Data="10" />
<!-- or -->
<my:MyControl Data="{Binding SomeData}" />
It's codebind is this:
public partial class MyControl : UserControl
{
public MyControl() {
InitializeComponent();
}
public const string DataPropertyName = "Data";
public int Data
{
get
{
return (int)GetValue(DataProperty);
}
set
{
SetValue(DataProperty, value);
}
}
public static readonly DependencyProperty DataProperty = DependencyProperty.Register(
DataPropertyName,
typeof(int),
typeof(MyControl),
new PropertyMetadata(10);
}
It's xaml portion is this:
<UserControl>
<!-- omitted namespaces etc. -->
<Grid x:Name="LayoutRoot">
<Button x:Name="myButton" Content="{Binding Data}">
<Button.Style>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<TextBlock Text="{TemplateBinding Content}" />
</ControlTemplate>
</Setter.Value>
</Button.Style>
</Button>
</Grid>
</UserControl>
The crucial line, in usercontrol's xaml part is:
<Button x:Name="myButton" Content="{Binding Data}">
I'd like to bind this Button's Content property to the UserControl's property (Data), while still retaining the ability to set values on it from outside (<my:MyControl Data="10" />)
The problem is, that when I use binding - <Button x:Name="myButton" Content="{Binding Data}"> - it doesn't work (The templatebinding doesnt pick any values)
It works however, if I set the values manually i.e - <Button x:Name="myButton" Content="12">
If you want to bind to your "own" dependency property inside a UserControl you need to add a x:Name to your UserControl and use it as the ElementName in your binding.
<UserControl x:Name="myControl">
<!-- omitted namespaces etc. -->
<Grid x:Name="LayoutRoot">
<Button x:Name="myButton"
Content="{Binding Data, ElementName=myControl}">
</Button>
</Grid>
</UserControl>
To make the Template also work:
Instead of the TemplateBinding you need to use the RelativeSource TemplatedParent sysntax, because you need to set the Mode=OneWay (TemplateBinding uses Mode=OneTime for performance reasons by default but in your scenario you need Mode=OneWay)
<Style TargetType="Button">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<TextBlock Text="{Binding Path=Content, Mode=OneWay,
RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
I have a user control in a DataTemplate, The Style of a TextBlock doesn't change the FontSize but changes the Background.
Attached are the samples:
Create a WPF window.
Create a User control, UserControl1
Inside the Window paste the below code:
<Window.Resources>
<Style TargetType="{x:Type TextBlock}"
x:Key="TextBlockStyleFontAndBackgound">
<Setter Property="FontSize"
Value="20" />
<Setter Property="Background"
Value="Blue" />
</Style>
<DataTemplate x:Key="contentTemplate">
<StackPanel>
<m:UserControl1 />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl FontSize="10">
<StackPanel x:Name="stackPanel">
<Button Click="Button_Click" />
<ContentControl ContentTemplate="{StaticResource contentTemplate}" />
<!--<m:UserControl1 />-->
</StackPanel>
</ContentControl>
</Grid>
In the user control paste the following code:
<UserControl.Resources>
<DataTemplate x:Key="contentTemplateInsideUserControl">
<TextBlock Name="textBlockInResourse" Text="textBlockInsideUserControlResource"
Style="{DynamicResource TextBlockStyleFontAndBackgound}"/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<StackPanel>
<ContentControl ContentTemplate="{StaticResource contentTemplateInsideUserControl}" />
<Button Content="St" Click="Button_Click" />
<TextBlock Name="textBlockInControl" Text="textBlockInsideUserControl"
Style="{DynamicResource TextBlockStyleFontAndBackgound}" />
</StackPanel>
</Grid>
We have 2 text blocks with the same background color, blue, but with different font sizes.
textBlockInResourse FontSize = 20, taken from the style TextBlockStyleFontAndBackgound
textBlockInControl FontSize = 10, inherited value, why does it happen?
I have added a handle in the user control:
private void Button_Click(object sender, RoutedEventArgs e)
{
Style style = FindResource("TextBlockStyleFontAndBackgound") as Style;
textBlockInControl.Style = null;
textBlockInControl.Style = style;
}
And now the Font is set to the style TextBlockStyleFontAndBackgound, and it's size is 20
Why now the FontSize is taken from the style TextBlockStyleFontAndBackgound.
Thanks,
barak
That's a very peculiar problem you have found there. I'm not sure why the FontSize is not affected when not in a DataTemplate... looking at the two property descriptions and remarks on MSDN, the only difference between them is that TextBlock.FontSize is also an AttachedProperty, but I can't see how that would affect anything.
I can however offer a solution to the problem if you're still interested. Try declaring your Style in your App.xaml file:
<Application.Resources>
<Style TargetType="{x:Type TextBlock}" x:Key="TextBlockStyleFontAndBackgound">
<Setter Property="FontSize" Value="20" />
<Setter Property="Background" Value="Blue" />
</Style>
</Application.Resources>
Then declare your TextBlock in your UserControl using StaticResource like so:
<TextBlock Text="text" Style="{StaticResource TextBlockStyleFontAndBackgound}" />