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.
Related
In a WPF project I am creating a wizard comprising of many steps. Each step has its own view. Currently, each view has the same outer XAML elements, like a TextBlock for the title of the step, but different content below that.
So, for example a typical view would look like:
STEP X VIEW
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Step X text goes here"/>
<Grid Grid.Row="1">
<!-- CONTENT OF STEP X GOES HERE -->
</Grid>
</Grid>
What I would like to do is be able to 'factor out' the common outer XAML for each step into another view and just place the content of each step as the content of that new factored out view. That way, I can avoid the repetition of the same outer XAML, and if I decide to change the implementation of each step title, I only have to do it in one place.
I'm sure ContentPresenter is the part of the solution. So, something like:
WRAPPERVIEW:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Step X text goes here"/>
<Grid Grid.Row="1">
<ContentPresenter Content="Content of Step X ??????"/>
</Grid>
</Grid>
My questions are:
1) Is my implementation of WrapperView the right way to go?
2) What is the binding for the ContentPresenter content property in WrapperView?
3) How can I customise the Text of the TextBlock for each step using WrapperView?
4) How can I get the client XAML using WrapperView for the XAML of any step view to look like.
So, using WrapperView for StepXView ,
New STEP X VIEW
<Grid>
<WrapperView TitleText ="Step X Title Text">
<!-- XAML for Step X View -->
</WrapperView>
</Grid>
You had a good intuition about using a ContentPresenter. To me, it looks exactly like you need a HeaderedContentControl, which is a templatable Control which handles both a Header and a Content property.
Although you could directly use HeaderedContentControl, I find it clearer to inherit from it in case you want to customize it further later:
public class WrapperView : HeaderedContentControl { }
And in your XAML, you can template it and use it as you wish:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1">
<Window.Resources>
<Style TargetType="{x:Type local:WrapperView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:WrapperView">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ContentPresenter HorizontalAlignment="Center" Grid.Row="0" Content="{TemplateBinding Header}"/>
<Grid Grid.Row="1" HorizontalAlignment="Center">
<ContentPresenter Content="{TemplateBinding Content}"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<local:WrapperView Header ="Step X Title Text">
<Rectangle Width="50" Height="50" Fill="Blue"/>
</local:WrapperView>
</Grid>
</Window>
The key here is the use of TemlateBinding which answers your second question.
To make it even cleaner, you could move this Style away from your Window.Resources and set it in Themes\Generics.xaml so that the Style gets applied by default.
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>
I've a problem to connect.
I started to connect my tabs with a tabcontrol.ressources and it worked to show the text of each tabs.
Then I wanted to had a scroll for my TabItems and it doesn't work, nothing shows in tab... I can't even use tabcontrol.ressources anymore...
<DockPanel>
<Button Background="DarkGoldenrod" Height="Auto" Command="{Binding OpenFlyoutDataCommand}">
<StackPanel>
<materialDesign:PackIcon Kind="ArrowRightBoldCircleOutline" Width="30" Height="30"/>
</StackPanel>
</Button>
<TabControl ItemsSource="{Binding TabEDCWaferData, Mode=TwoWay}"
SelectedItem="{Binding SelectedTabEDCWaferData}">
<!-- Used to create a scroolbar for tabitems -->
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Hidden" >
<TabPanel Grid.Column="0" Grid.Row="0"
Margin="2,2,2,0" IsItemsHost="true"/>
</ScrollViewer>
<ContentPresenter ContentSource="..."/>
</Grid>
</ControlTemplate>
</TabControl.Template>
<!-- Contains the text in the tab item ! -->
<TabControl.Resources>
<DataTemplate DataType="TabItem">
<DockPanel>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Content}" />
</DockPanel>
</DataTemplate>
</TabControl.Resources>
</TabControl>
</DockPanel>
This is connected to a collection of TabItem, where I've a function to add Items binding to an other button.
private ObservableCollection<TabItem> _TabEDCWaferData;
public ObservableCollection<TabItem> TabEDCWaferData
{
get { return _TabEDCWaferData; }
set
{
_TabEDCWaferData = value;
RaisePropertyChanged("TabEDCWaferData");
}
}
public void AddTabItem(string name)
{
TabItem tab = new TabItem();
tab.Header = name;
tab.Content = "Temporary content";
TabEDCWaferData.Add(tab);
}
I read that I have to use the ContentPresenter, but I don't know how to bind it. I think this is not working with TabItems...
I just want to bind it as I did in the Ressources by using the ContentPresenter.
I hope that I'm clear enough ! Thanks
EDIT : I try to display in the ContentPresenter the selected item tab content that I add in the function `AddTabItem.
With ContentPresenter, most times, this does the job:
<ContentPresenter />
The default ContentSource is "Content". That means it'll look at the Content property of the templated parent and it'll take whatever it finds there for its own content.
But that doesn't help you at all, and you don't have to use ContentPresenter; it's just a convenience. In this case, the content you want to present is SelectedItem.Content, which isn't a valid ContentSource for ContentPresenter. But you can do the same thing with a binding on a ContentControl instead:
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ScrollViewer
Grid.Row="0"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Hidden"
>
<TabPanel
Grid.Column="0"
Margin="2,2,2,0" IsItemsHost="true"/>
</ScrollViewer>
<ContentControl
Grid.Row="1"
Content="{Binding SelectedItem.Content, RelativeSource={RelativeSource TemplatedParent}}"
/>
</Grid>
</ControlTemplate>
</TabControl.Template>
TemplateBinding isn't going to work with a Path such as "SelectedItem.Content"; it only accepts names of properties on the templated parent. I fixed your Grid.Row attributes, too.
Also, you may as well delete that DataTemplate for TabItem that you put in TabControl.Resources. That's not what DataTemplate is for; you use DataTemplates to define visual presentations for your viewmodel classes, but TabItem is a control. It already knows how to display itself, and in fact that DataTemplate is being ignored, so it's best not to leave it there; you'll only waste time later on making changes to it and trying to figure out why it's not having any effect. Your TabItems will display correctly without it.
Try something like this ?
<ContentPresenter Content="{TemplateBinding Content}" />
Edit
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" />
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.
I'm so new to this that I can't even phrase the question right...
Anyway, I'm trying to do something very simple and have been unable to figure it out. I have the following class:
public class Day : Control, INotifyPropertyChanged
{
public static readonly DependencyProperty DateProperty =
DependencyProperty.Register("Date", typeof(int), typeof(Day));
public int Date
{
get { return (int)GetValue(DateProperty); }
set
{
SetValue(DateProperty, value);
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Date"));
}
}
}
public static readonly DependencyProperty DayNameProperty =
DependencyProperty.Register("DayName", typeof(String), typeof(Day));
public String DayName
{
get { return (String)GetValue(DayNameProperty); }
set
{
SetValue(DayNameProperty, value);
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("DayName"));
}
}
}
static Day()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Day), new FrameworkPropertyMetadata(typeof(Day)));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
I've learned that you can't call a constructor that has parameters in XAML so the only way to actually set some data for this class is through the two properties, DayName and Date.
I created a ControlTemplate for Day which is as follows:
<Style TargetType="{x:Type con:Day}">
<Setter Property="MinHeight" Value="20"/>
<Setter Property="MinWidth" Value="80"/>
<Setter Property="Height" Value="20"/>
<Setter Property="Width" Value="80"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type con:Day}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle Grid.ColumnSpan="2" x:Name="rectHasEntry" Fill="WhiteSmoke"/>
<TextBlock Grid.Column="0" x:Name="textBlockDayName" Text="{TemplateBinding DayName}" FontFamily="Junction" FontSize="11" Background="Transparent"
HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,2,0,0"/>
<TextBlock Grid.Column="1" x:Name="textBlockDate" Text="{TemplateBinding Date}" FontFamily="Junction" FontSize="11" Background="Transparent"
HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,2,0,0"/>
<Rectangle Grid.ColumnSpan="2" x:Name="rectMouseOver" Fill="#A2C0DA" Opacity="0"
Style="{StaticResource DayRectangleMouseOverStyle}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I then render it on screen in my MainWindow thusly:
<Window x:Class="WPFControlLibrary.TestHarness.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:con="clr-namespace:WPFControlLibrary.Calendar;assembly=WPFControlLibrary"
Title="MainWindow" Height="500" Width="525"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<con:Day Grid.Column="1" Height="20" Width="80" DayName="Mon" Date="1"/>
</Grid>
And what I actually see is, well, nothing. If I put my cursor on the con:Day line of the XAML it'll highlight the correctly sized rectangle in the window but I don't see "Mon" on the left side of the rectangle and "1" on the right.
What am I doing wrong? I suspect it's something simple but I'll be darned if I'm seeing it.
My ultimate goal is to group a bunch of the Day controls within a Month control, which is then contained in a Year control as I'm trying to make a long Calendar Bar that lets you navigate through the months and years, while clicking on a Day would display any information saved on that date. But I can't even get the Day part to display independent of anything else so I'm a long way from the rest of the functionality. Any help would be greatly appreciated.
First of all, you don't need to implement INotifyPropertyChanged if you have DependencyProperties. The following is more than enough:
public int Date
{
get { return (int)GetValue(DateProperty); }
set { SetValue(DateProperty, value); }
}
When I try your example (without rectMouseOver, since I don't have the definition of DayRectangleMouseOverStyle), Mon shows just fine but 1 does not show up. I was able to fix that by replacing TemplateBinding with an explicit binding: Instead of
{TemplateBinding Date}
use
{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Date}
After that change, your example worked fine. I don't know why, but TemplateBinding seems to be "broken" sometimes.
You need to setup a DataTemplate for your class, not a ControlTemplate.
DataTemplates are used to define how a custom class is diplayed. ControlTemplates, via Style, is used to stylize a control.
For details, I recommend reading the Data Templating Overview on MSDN.
You're approaching this from an angle that is going to give you nothing but grief. I know this because I did exactly what you're trying to do once.
Consider approaching it this way: You have a Day class that exposes DayName, DayNumber, and Column properties. You can then create a data template for that class:
<DataTemplate DataType="{x:Type local:Day}">
<TextBlock Grid.Column="{Binding Column}"
Text="{Binding DayNumber}"
ToolTip="{Binding DayName}"/>
</DataTemplate>
Now create a Week class that contains a collection of Day objects. Create a template for that class:
<DataTemplate DataType="{x:Type local:Week}">
<ItemsControl ItemSource={Binding Days}>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
And a Month class that contains a collection of Week objects. Its data template looks like this:
<DataTemplate>
<ItemsControl ItemsSource="{Binding Weeks}"/>
</DataTemplate>
(You don't need to create an ItemsPanelTemplate here because the template that ItemsPanel uses by default, a vertical StackPanel, is the one you want.
Now if you create an instance of a Month object (and populate its weeks and days correctly), anywhere in your XAML that WPF needs to render it, it will create a vertical StackPanel containing several Grids, each of which contains seven TextBlocks with the appropriate day numbers in them.
Creating a Year object and a template for it I'll leave as an exercise for you. Eventually you'll add ScrollViewers and styling to these templates, and implement more properties in the object model to help with the UI. For instance, if you want a day to display differently if it has information, you might add a HasInformation property to the Day class, and implement a data trigger to change its background color or font weight if it's true. And you'll implement Command objects for the things you actually want this to do, like display the information for a specific day. You'll get there.