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 Grid
s, each of which contains seven TextBlock
s 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 ScrollViewer
s 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.
精彩评论