Customize nested control inside a BasedOn style - c#

I'm approaching to WPF (in particular I'm working on MVVM) and I'm trying to creating a custom Window style that will be used throughout the application. What I want to do is create a base style that will define the window color, the border, icon, title, etc. The Window can be resizable or dialog like, so I used WindowChrome to set up the "resizable window" that has minimize, maximize and close buttons by default and that's, in fact, resizable. For the Login window I would like to have a window that uses the base style, but the user can't resize or maximize it, so the maximize button should not be visible at all. I've been working on BasedOn styles and I can override properties succesfully, but I can't manage to define which buttons can or can't be visible inside the window. So what I'm trying to do is changing a nested UI control (a StackPanel in this case).
Here is the base style I created, that contains, for now, all the window properties and the window buttons as well (I tried to comment it the best I could):
<ControlTemplate TargetType="{x:Type Window}" x:Key="DefaultWindowsTemplate">
<!-- The outer border of the Window -->
<Border Padding="{Binding OuterMarginSizeThickness, FallbackValue=10}">
<!-- The inner border of the Window and the Window itself, from the contour line to the shadow -->
<Grid>
<Border CornerRadius="{Binding WindowCornerRadius}"
BorderBrush="{StaticResource AlizarinBrush}"
BorderThickness="{Binding OutlineBorderThickness, FallbackValue=1}"
Background="{StaticResource VoidnessBrush}">
<Border.Effect>
<DropShadowEffect Color="{StaticResource Voidness}" ShadowDepth="0" Opacity="1"/>
</Border.Effect>
</Border>
<!-- The Container grid, composed by the title bar and the content area -->
<Grid>
<!-- Rows definition -->
<Grid.RowDefinitions>
<RowDefinition Height="{Binding TitleHeight, FallbackValue=30}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Title bar row that contains icon, title and window buttons -->
<Grid Margin="{Binding TitleHeightMargin}"
Background="{StaticResource VoidnessBrush}"
Grid.Row="0"
Panel.ZIndex="1"
>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Window icon -->
<Button Margin="5 5 0 0"
Style="{StaticResource WindowIconButtonStyle}"
Command="{Binding MenuCommand}">
<Image Source="/Images/Logos/khm_logo_titlebar.png"/>
</Button>
<!-- Window title -->
<TextBlock Grid.Column="1"
Foreground="{StaticResource ConcreteBrush}"
Margin="15 5 0 0"
TextAlignment="Center"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Title, FallbackValue='Window Title'}"/>
<!-- Window buttons - THIS IS THE CONTROL I WANT TO DEFINE INSIDE 'BASED ON' STYLES, WHERE I WILL NOT HAVE THE MAXIMIZE BUTTON -->
<StackPanel Grid.Column="2"
Orientation="Horizontal">
<Button Style="{StaticResource WindowButtonsStyle}"
Content="0"
Command="{Binding MinimizeCommand}"/>
<ToggleButton Style="{StaticResource MaximizeWindowButtonStyle}"
Command="{Binding MaximizeCommand}"/>
<Button Style="{StaticResource WindowCloseButtonStyle}"
Content="r"
Command="{Binding CloseCommand}"/>
</StackPanel>
</Grid>
<!-- The Window content -->
<Grid Margin="1 5 0 0" Grid.Row="1">
<ContentPresenter/>
</Grid>
</Grid>
</Grid>
</Border>
</ControlTemplate>
<Style TargetType="Window" x:Key="DefaultWindowsStyle">
<Setter Property="Template" Value="{StaticResource DefaultWindowsTemplate}"/>
<Setter Property="MinWidth" Value="{Binding WindowMinWidth}"/>
<Setter Property="MinHeight" Value="{Binding WindowMinHeight}"/>
<Setter Property="WindowStyle" Value="None"/>
<Setter Property="AllowsTransparency" Value="True"/>
</Style>
Then I start editing the base style as following (in another XAML file of course):
<Style TargetType="Window" x:Key="DialogWindowsStyle" BasedOn="{StaticResource DefaultWindowsStyle}">
<!-- REMOVE THE MAXIMIZE BUTTON INSIDE THE NESTED STACK PANEL -->
</Style>
So what is the right way to edit part of the UI while using the same style?
Thank you in advance for the help.

I would define boolean attached properties local:WindowExt.ShowMaximizeButton etc. with default values of true. In the ControlTemplate I'd apply those to the buttons with TemplateBindings, and set them via style setters (or directly on Window elements in XAML).
Here's an example of ShowMaximizeButton; the others are just the same thing with different names. When you copy and paste the dependency property definition, be careful you update the property name every place it appears. I use snippets to create those, to minimize careless errors.
public static class WindowExt
{
public static bool GetShowMaximizeButton(Window obj)
{
return (bool)obj.GetValue(ShowMaximizeButtonProperty);
}
public static void SetShowMaximizeButton(Window obj, bool value)
{
obj.SetValue(ShowMaximizeButtonProperty, value);
}
public static readonly DependencyProperty ShowMaximizeButtonProperty =
DependencyProperty.RegisterAttached("ShowMaximizeButton", typeof(bool), typeof(WindowExt),
new PropertyMetadata(true));
}
Make sure this is somewhere prior to where the window control template is defined, in the same resource dictionary:
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
The maximize button in the window control template. There won't be any change to the control template other than adding these appropriately bound visibility attributes to the buttons. Note the parens in the Binding's Path; those are critical because it's a multi-part identifier for a single property.
<ToggleButton
Style="{StaticResource MaximizeWindowButtonStyle}"
Command="{Binding MaximizeCommand}"
Visibility="{TemplateBinding local:WindowExt.ShowMaximizeButton, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
And usage in the window style:
<Style TargetType="Window">
<Setter Property="local:WindowExt.ShowMaximizeButton" Value="True" />
</Style>
Note that attached properties are dependency properties of the control itself, nothing to do with any DataContexts or viewmodels.

Related

How do i get a Menu in the Window bar?

I would like to know how to get a menu in the window bar, the same as Visual studio does.
It would be good to be able to have File, Edit, etc on the left and the standard Minimize, Maximize and Close buttons on the right. Is this at all possible?
I have tried setting Window WindowStyle="None" and adding my own icons in the bar but it doesnt seem right but is this the only way?
This is what i have at the moment.
<Window
Title="MainWindow"
Height="{x:Static SystemParameters.PrimaryScreenHeight}"
Width="{x:Static SystemParameters.PrimaryScreenWidth}"
Closing="Window_Closing"
WindowState="Maximized">
You must create your custom window chrome using the WindowChrome class:
The Window element in WPF is actually hosted in a non-WPF (non-client) host. This host includes the title bar (caption) and the standard buttons, an icon and the actual frame. This is known as window chrome.
Usually you can only modify the client area of a Window. But whith the help of the WindowChrome class, WPF allows the client area to extend into the non-client area.
The disadvantage is that you would have to basically replicate the original non-client area in order to preserve the original look and feel. But after all you still get some basic behavior like maximizing the window on double click ou of the box.
The following example is very basic, but should give you an idea how to achieve your task:
I highly recommend to follow the provided link to the WindowChrome class and read the remarks section, which contains very valuable information.
You can use the actual system values provided by the static SystemParameters class to get the current dimension values e.g. SystemParameters.WindowResizeBorderThickness which you should use in your custom chrome style.
Also note that to allow WPF to capture mouse events on you custom chrome's input elements like buttons or menus you must set the attached property WindowChrome.IsHitTestVisibleInChrome on each relevant element to true:
WindowChrome.IsHitTestVisibleInChrome="True"
The very basic style that creates the above visual is as followed:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework">
<Style x:Key="WindowStyle" TargetType="{x:Type Window}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome NonClientFrameEdges="Right"
ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid Background="Transparent">
<AdornerDecorator>
<Border Background="Transparent" Margin="{x:Static SystemParameters.WindowNonClientFrameThickness}">
<ContentPresenter />
</Border>
</AdornerDecorator>
<ResizeGrip x:Name="WindowResizeGrip"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Visibility="Collapsed"
IsTabStop="false" />
<Grid Height="{Binding Source={x:Static SystemParameters.WindowNonClientFrameThickness}, Path=Top}"
Background="#FF3F3F3F"
VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Custom window chrome -->
<StackPanel Grid.Column="0" Orientation="Horizontal"
Margin="{x:Static SystemParameters.WindowResizeBorderThickness}">
<Image Source="{TemplateBinding Icon}" />
<TextBlock Text="{TemplateBinding Title}"
TextTrimming="CharacterEllipsis"
VerticalAlignment="Center"
Margin="16,0" />
<Menu shell:WindowChrome.IsHitTestVisibleInChrome="True">
<MenuItem Header="CustomChromeMenu">
<MenuItem Header="Action" />
</MenuItem>
</Menu>
</StackPanel>
<StackPanel Grid.Column="1"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="45"
Height="{Binding Source={x:Static SystemParameters.WindowNonClientFrameThickness}, Path=Top}"
ToolTip="Minimize window"
ToolTipService.ShowDuration="5000"
shell:WindowChrome.IsHitTestVisibleInChrome="True">
<TextBlock Foreground="{Binding RelativeSource={RelativeSource AncestorType=Control}, Path=Foreground}"
FontFamily="Segoe MDL2 Assets"
FontSize="11"
Text="" />
</Button>
<Button ToolTip="Maximize window"
Width="45"
Height="{Binding Source={x:Static SystemParameters.WindowNonClientFrameThickness}, Path=Top}"
ToolTipService.ShowDuration="5000"
shell:WindowChrome.IsHitTestVisibleInChrome="True">
<TextBlock Foreground="{Binding RelativeSource={RelativeSource AncestorType=Control}, Path=Foreground}"
FontFamily="Segoe MDL2 Assets"
FontSize="11"
Text="" />
</Button>
<Button ToolTip="Close application"
ToolTipService.ShowDuration="5000"
Width="50"
Height="{Binding Source={x:Static SystemParameters.WindowNonClientFrameThickness}, Path=Top}"
shell:WindowChrome.IsHitTestVisibleInChrome="True">
<TextBlock Foreground="{Binding RelativeSource={RelativeSource AncestorType=Control}, Path=Foreground}"
FontFamily="Segoe MDL2 Assets"
FontSize="11"
Text="" />
</Button>
</StackPanel>
</Grid>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="ResizeMode"
Value="CanResizeWithGrip">
<Setter TargetName="WindowResizeGrip"
Property="Visibility"
Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
The title bar contains
An icon
A title
A minimize button
A maximize button
A close button
And that's all. You can't add anything.
In VS, the title bar is masked. The "title bar" you see is in fact the content of the window. That is, as you seemed to suspect, the only way.
That's also why tooltips on the three right buttons are in the OS language for most apps (because the title bar is managed by the system), but are in the app language for Visual Studio.
You have to set WindowStyle to None to mask the real title bar.
Then, inside your window, you should add a DockPanel and dock to the top an image and a menu on the left, and 3 buttons on the right.
The minimize button should change the WindowState to Minimized.
The maximize button should change the WindowState to either Normal or Maximized and its icon should be based on the WindowState.
The close button should call the Close() method and/or the Application.Current.Shutdown(0) method.
You should also subscribe to events like MouseLeftButtonDown, MouseLeftButtonUp and MouseMove to move the window.

Closable tab with tab item being a user control

I am working on a project in C# WPF. I have a tab container and I want to dynamically load different types of tabs into the tab container as the user requires. As an example I am doing something like the following:
tabContainer.Items.Add(new MyUserControl());
I want each tab to have a close button so the tab can be removed the container when the user no longer requires it.
I found this code project example but from what I can see you are a loading a user control which contains the xaml for the tab itself, not the tab content or am I missing something.
How can I load in my User Control into the tab container, but also have the tab closable.
Currently the tab that I am loading in uses some static binding to set the tab title using the following:
<TabControl x:Name="tabContainer" Grid.Column="2" Margin="10,45,0,0" RenderTransformOrigin="0.5,0.55" Grid.ColumnSpan="3">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
</Style>
</TabControl.Resources>
</TabControl>
My user control then has a `public string TabHeader{get;set;} which gets set in the constructor depending on what constructor of my user control is used.
You will have to define the close Button yourself. You could for example do this in the HeaderTemplate of the TabItem:
<TabControl x:Name="tabContainer">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Button Content="x" Click="Button_Click_2"
Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType=TabItem}}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
</TabControl>
The Tag property is bound to the UserControl in the Items collection which you can remove in the click event handler of the Button, like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
tabContainer.Items.Add(new MyUserControl());
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
tabContainer.Items.Remove(button.Tag);
}
}
If you want to add a close button to each tab, that would be in the TabItem style ControlTemplate. Normally you'd specify the data context (i.e. the data only that's driving the content) in Content and then specify the look in ContentTemplate. If your Content is a UserControl then you don't specify the ContentTemplate since a UserControl knows how to draw itself.
For my sins, I've added close-tab buttons to the WPF TabControl. I ended up putting the close button in the ItemTemplate. Here's a minimal version that works with the way you're populating the TabControl and the header content:
<TabControl
>
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
</Style>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Content="{Binding}"
Grid.Column="0"
/>
<Button
VerticalAlignment="Center"
Grid.Column="1">
<Path
Data="M 0, 0 L 12, 12 M 12,0 L 0,12"
Stroke="Red"
StrokeThickness="2"
Width="12"
Height="12"
/>
</Button>
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
<local:UserControl1 TabHeader="First Item" />
<local:UserControl1 TabHeader="Second Item" />
</TabControl>

WPF ListBoxItem ControlTemplate breaks some MouseDown/Selection

I have an issue with ListBoxItems. I am trying to make all controls in the ListBoxItem select it as well, so clicking on a TextBox, Label, etc will select the ListBoxItem. Pretty simple so far.
I am also changing the ListBoxItem Template to change the selection visualization from highlighting the background to just drawing a border. Also pretty simple.
The combination of these two, however, seems to cause some really irritating issues with MouseDown and PreviewMouseDown, specifically in my case regarding Labels in a Grid, where one creates a "void" occupied by Grid space.
Using snoop, I can see the PreviewMouseDown event stopping at the ScrollViewer inside the ListBox, and not going all the way to the ListBoxItem.
XAML:
<Window x:Class="ListBoxClickThroughTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350">
<Grid>
<ListBox ItemsSource="{Binding Items}"
SelectionMode="Single">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Name="VerySuperLongLabel"
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Left"
Content="VerySuperLongLabel"
Padding="0" />
<TextBox Name="Textbox1"
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Right"
Text="Textbox1 Text" />
<Label Name="ShortLabel"
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Left"
Content="ShortLabel"
Padding="0" />
<TextBox Name="Textbox2"
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Right"
Text="Textbox2 Text" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="PreviewMouseDown"
Handler="ListBoxItem_PreviewMouseDown" />
<EventSetter Event="MouseDown"
Handler="ListBoxItem_PreviewMouseDown" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="Bd"
BorderThickness="1">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Bd" Property="BorderBrush" Value="Gray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</Window>
Code-behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace ListBoxClickThroughTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
Items = new List<string>() { "1", "2" };
InitializeComponent();
DataContext = this;
}
public List<string> Items { get; set; }
private void ListBoxItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var listBoxItem = (ListBoxItem)sender;
listBoxItem.IsSelected = true;
}
}
}
However, if I remove the Template setter, all is well. Is there some magic in the template I'm missing? I tried renaming the border to "Bd" as that was what the default template border was named, but no luck. Any ideas?
If you change the horizontal alignment of the labels from "Left" to "Stretch" this will fix the problem and keep the visual formatting the same.
Mousedown events only work in areas where elements exist. By having the labels at a "left" horizontal alignment, you are creating the "void" you mentioned, where no element exists at that level that can be clicked. To visually see the difference, try temporarily setting the background property of the label elements that are giving you problems, and you'll see the element doesn't extend all the way to the textbox.

WPF What is the difference between setting the background to transparent and setting opacity on the control?

So I realized that my graph viewer had the axis displaying over the actual items in the graph, so I changed the ZIndex on the grid to display the items over the axis instead.
However, I noticed that I couldn't actually see anything under the actual items because the background of the items were opaque. I think I have two options then, to either set the background of the items to transparent, or to set the opacity of the items. Is there any difference between these two options?
<Grid
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
Grid.ZIndex="1"
>
<Components:SignalGraphAxis
x:Name="signal_axis"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
GraphHeight="{Binding Path=GraphHeight}"
PenColor="{Binding Path=AxisColor, Mode=OneWay}"
PenWidth="{Binding Path=GraphPenWidth, Mode=OneWay}"
MinHeight="10"
MinWidth="10"
AxisTimeScale="{Binding Path=GraphTimeScale}"
NumberOfPixelsPerDivision="{Binding Path=NumberOfPixelsPerDivision, Mode=OneWay}"
MinDisplayValue ="{Binding Path=MinDisplayValue, Mode=OneWay}"
UnitsOfGraphTimePerInch="{Binding Path=UnitsOfTimePerInch, Mode=OneWay}"
/>
</Grid>
<ScrollViewer
x:Name="signal_scrollviewer"
Grid.Row="1"
Grid.RowSpan="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.ZIndex="2"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible"
CanContentScroll="True"
Style="{StaticResource SignalScrollViewerStyle}"
>
<ItemsPresenter />
</ScrollViewer>
</Grid>
Background property is defined on Control class and Opacity is defined much higher on UIElement.
From MSDN Page Control.Background Property
This property only affects a control whose template uses the
Background property as a parameter. On other controls, this property
has no impact.
Let's try to create a Custom Control to see how this works.
CustomControl1.cs
public class CustomControl1 : ContentControl
{
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
}
Default Template For CustomControl1
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="My Custom Control " Grid.Row="0" />
<ContentPresenter Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Please note, the above template doesn't use Background property at all in it's Template.
Now, Let's try to use that in a Form and see how it behaves:
Code from Window1.xaml
<Grid>
<wpfApplication5:CustomControl1 Background="Green">
<Button Content="Button Within Custom Control" Margin="25"/>
</wpfApplication5:CustomControl1>
</Grid>
The resultant output:
See, there was no Green background for the rendered CustomControl even though we set the Background to Green in Window1.xaml.
Now, Lets modify the template to use Background property.
Template with Background Property
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Grid Background="{TemplateBinding Background}">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="My Custom Control " Grid.Row="0" />
<ContentPresenter Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And the resultant output will now have a the specified background applied.
I think (couldn't find any references though), Opacity gets applied to the element/Control whether the Control's Template worry about Opacity property or not.
Window1.xam with Opacity Set on CustomControl
<Grid>
<wpfApplication5:CustomControl1 Background="Green" Opacity="0.2">
<Button Content="Button Within Custom Control" Margin="25"/>
</wpfApplication5:CustomControl1>
</Grid>
and resultant Output
See, the Opacity got applied even though our Custom Control's template doesn't worry anything about Opacity property.
Finally, to answer your question: Though either setting Opacity to 0 or Background to Transparent may give you the same visual result. But, for Background property, it totally depends on the Control implementation and how it handles Background property. Whereas, with Opacity it gets applied from parent elements to down the elements tree to child elements irrespective of the control.
Refer to MSDN Page, UIElement.Opacity Property to read more on Opacity property and how it behaves when Opacity is set at multiple levels in an element tree.

Binding command to a templated user control

I'm new to WPF, but have been able make a lot of progress in short time thanks to a good book on the topic, and of course, quality posts on sites like this one. However, now I've come across something I can seem to figure out by those means, so I posting my first question.
I've have a ControlTemplate in a resource dictionary which I apply to several UserControl views. The template provides a simple overlay border and two buttons: Save and Cancel. The templated user control holds various text boxes, etc., and is bound to some ViewModel depending on the context. I'm trying to figure out how to bind the commands to the Save/Cancel buttons when I use/declare the UserControl in some view. Is this is even possible, or am I doing something very wrong?
First, the template:
<ControlTemplate x:Key="OverlayEditorDialog"
TargetType="ContentControl">
<Grid>
<Border HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="DarkGray"
Opacity=".7"/>
<Border HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="DarkGray">
<Grid>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0"/>
<Grid Grid.Row="1"
Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1"
Content="Cancel"
***Command="{Binding CancelCommand}}"**
/>
<Button Grid.Column="0"
Content="Save"
***Command="{Binding Path=SaveCommand}"***/>
</Grid>
</Grid>
</Border>
</Grid>
</ControlTemplate>
The template in turn is used in the CustomerEditorOverlay user control
<UserControl x:Class="GarazhApp.View.CustomerEditorOverlay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<UserControl.Resources>
<ResourceDictionary Source="Dictionary1.xaml"/>
</UserControl.Resources>
<ContentControl Template="{StaticResource ResourceKey=OverlayEditorDialog}">
<Grid Grid.Row="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<SomeElement/>
<SomeOtherElement/>
</Grid>
</ContentControl>
...and finally, the user control is used as part of a view like so:
<local:CustomerEditorOverlay Visibility="{Binding Path=CustomerViewModel.ViewMode, Converter={StaticResource myConverter}, FallbackValue=Collapsed}"
d:IsHidden="True" />
So, based on what I've learned from a project I have been on forever and a half, we have a workable pattern.
Let's say you have a bunch of modal windows that all get applied the same style within the application. To have Save and Cancel buttons on each view, the UserControl used for all of the modal windows has several dependency properties. In addition, we specify virtual methods for your commands (e.g. OnSaveCommand, OnCancelCommand, CanExecuteSaveCommand, CanExecuteCancelCommand) and the commands themselves as properties in a base ViewModel that is inherited by your views.
Ultimately, what happens is we create new modal windows by simply doing this:
<my:YourBaseView x:class="MyFirstView" xmlns:whatever="whatever" [...]>
<my:YourBaseView.PrimaryButton>
<Button Content="Save" Command="{Binding SaveCommand}" />
</my:YourBaseView.PrimaryButton>
<!-- some content -->
</my:YourBaseView>
With accompanying code-behind:
public class MyFirstView : YourBaseView
{
[Import] /* using MEF, but you can also do MvvmLight or whatever */
public MyFirstViewModel ViewModel { /* based on datacontext */ }
}
And a ViewModel:
public class MyFirstViewModel : ViewModelBase
{
public override OnSaveCommand(object commandParameter)
{
/* do something on save */
}
}
The template for this UserControl specifies ContentControls in a grid layout with the Content property bound to the PrimaryButton and SecondaryButton. Of course, the content for the modal is stored in the Content property of the UserControl and displayed in a ContentPresenter as well.
<Style TargetType="{x:Type my:YourBaseView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type my:YourBaseView}">
<Grid>
<!-- ignoring layout stuff -->
<ContentControl Content="{TemplateBinding Content}" />
<ContentControl Content="{TemplateBinding PrimaryButton}" />
<ContentControl Content="{TemplateBinding SecondaryButton}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
UserControl code:
public class YourBaseView : UserControl
{
public static readonly DependencyProperty PrimaryButtonProperty =
DependencyProperty.Register("PrimaryButton", typeof(Button), typeof(YourBaseView), new PropertyMetadata(null));
public Button PrimaryButton
{
get { return (Button)GetValue(PrimaryButtonProperty); }
set { SetValue(PrimaryButtonProperty, value); }
}
/* and so on */
}
You can change the style for each instance of your templated view, of course. We just happen to stick with one base style.
TL;DR edit: I may have gone a bit overboard since I think you just need the understanding that exposing dependency properties of type Button which are set up through the XAML each time you create a new overlay. That, or you could probably RelativeSource your way back up to the visual tree with something like {Binding DataContext.SaveCommand, RelativeSource={RelativeSource AncestorType={x:Type MyView}}} but it's a little dirtier.

Categories