tags:

views:

55

answers:

3

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.

A: 

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.

Reed Copsey
+2  A: 

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.

Heinzi
Still no luck. I removed the INotifyPropertyChanged code from the Day class and changed {TemplateBinding Date} (and DayName) to {Binding RelativeSource={RelativeSource TemplatedParent}, Path=Date} and I still don't see anything. I've tried this in both VS2008 Express and VS2010 RC with no luck. I've also removed the rectMouseOver Rectangle in case that was getting in the way but that did nothing.
Scott
@Scott: Strange. If you want to have a look at what works here, I've uploaded the project I used to test this: http://www.heinzi.at/temp/CsWpfApplication1.zip. I hope this will help you debug what's wrong...
Heinzi
Aha, I see it now. My styles are in a Resource Dictionary called Generic.xaml, while your style was in the Window.Resources of the MainWindow. When I moved the style to the MainWindow it worked. So apparently, my objects aren't finding the styles that I'd had in Generic.xaml as I'd thought they would via the TargetType property. Apparently my understanding of how that works is flawed. So if I have my styles in a different resource dictionary how do the objects that are to use those styles find them? I thought that's what setting the TargetType of the Control Template was for?
Scott
@Scott: Is your Generic.xaml in a subdirectory called `Themes`? That's important. (Related question: http://stackoverflow.com/questions/1228875)
Heinzi
@Heinzi, that did it. I moved Generic.xaml to a subdir called Themes and now I'm seeing what I'd expected to see. Thanks a ton, you really got me over a hump here. It always amazes me how you think the problem lies down a certain avenue only to find it's somewhere completely different.Thanks again.
Scott
A: 

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.

Robert Rossney