Reputation:
The code below is a mostly working control that can be used to display an object in a table with two rows. The first row is the header row and the second row displays the data. Properties of the object being displayed can be defined as the Items for the control. Each item is displayed as a column in the generated table. I do not want to use reflection to implement this control. This question is not about reflection.
The control has two problems:
1.) It is not written in "WPF form" (for lack of a better description) because I create objects in code rather than let the binding engine do the work. The reason I have it coded the way do is because I don't know how to code it correctly. Note I am using a grid for (what should be) the ItemsPanel for the control. I want to use a grid because I want the output to be in table form with borders. For every Item in the Items control I need to bind to two TextBlocks in the the first and second rows of the grid. I also need to add columns so each Item in the Items property shows up in a new column.
2.) The bindings created in the BuildDataCell method don't work. This problem will most likely be resolved if 1.) is resolved. In the unlikely event the control cannot be written in WPF form I would like to use it as-is if I can make the bindings work.
The article below shows up in several StackOverflow questions that I came across while researching this. It either does not answer my question or answers it but I don't know why. http://drwpf.com/blog/2009/05/12/itemscontrol-l-is-for-lookless/#TemplatingAnItemsControl This article is also relevant but I cannot apply it to this problem: http://blog.scottlogic.com/2010/11/15/using-a-grid-as-the-panel-for-an-itemscontrol.html
InfoTable.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace InfoTableHost
{
public class InfoTableItem : DependencyObject
{
public String Header
{
get { return (String)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(String), typeof(InfoTableItem), new PropertyMetadata(String.Empty));
public string Data
{
get { return (string)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(string), typeof(InfoTableItem), new PropertyMetadata(String.Empty));
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberNameAttribute] string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class InfoTable : ItemsControl
{
public enum ColumnSizeModes
{
AutoFitGrowLast, // Shrink to fit except last column which expands to fill parent.
UniformWidth, // All Columns are the same width. Table expands to fill parent
AutoFit // Shrink to fit. Table does not expand to fill parent
}
public ColumnSizeModes ColumnSizeMode
{
get { return (ColumnSizeModes)GetValue(ColumnSizeModeProperty); }
set { SetValue(ColumnSizeModeProperty, value); }
}
public static readonly DependencyProperty ColumnSizeModeProperty =
DependencyProperty.Register("ColumnSizeMode", typeof(ColumnSizeModes), typeof(InfoTable), new PropertyMetadata(ColumnSizeModes.AutoFitGrowLast));
public Style HeaderCellStyle
{
get { return (Style)GetValue(HeaderCellStyleProperty); }
set { SetValue(HeaderCellStyleProperty, value); }
}
public static readonly DependencyProperty HeaderCellStyleProperty =
DependencyProperty.Register("HeaderCellStyle", typeof(Style), typeof(InfoTable), new PropertyMetadata(null));
public Style HeaderTextStyle
{
get { return (Style)GetValue(HeaderTextStyleProperty); }
set { SetValue(HeaderTextStyleProperty, value); }
}
public static readonly DependencyProperty HeaderTextStyleProperty =
DependencyProperty.Register("HeaderTextStyle", typeof(Style), typeof(InfoTable), new PropertyMetadata(null));
public Style DataCellStyle
{
get { return (Style)GetValue(DataCellStyleProperty); }
set { SetValue(DataCellStyleProperty, value); }
}
public static readonly DependencyProperty DataCellStyleProperty =
DependencyProperty.Register("DataCellStyle", typeof(Style), typeof(InfoTable), new PropertyMetadata(null));
public Style DataTextStyle
{
get { return (Style)GetValue(DataTextStyleProperty); }
set { SetValue(DataTextStyleProperty, value); }
}
public static readonly DependencyProperty DataTextStyleProperty =
DependencyProperty.Register("DataTextStyle", typeof(Style), typeof(InfoTable), new PropertyMetadata(null));
private bool isTemplateApplied;
private Grid RootGrid;
static InfoTable()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(InfoTable), new FrameworkPropertyMetadata(typeof(InfoTable)));
}
public InfoTable()
{
Loaded += InfoTable_Loaded;
}
void InfoTable_Loaded(object sender, RoutedEventArgs e)
{
if (!isTemplateApplied)
return;
Loaded -= InfoTable_Loaded;
RootGrid = (Grid)GetTemplateChild("root");
for (int i = 0; i < Items.Count; i++)
{
ColumnDefinition cd = new ColumnDefinition();
if (ColumnSizeMode == ColumnSizeModes.UniformWidth || (ColumnSizeMode == ColumnSizeModes.AutoFitGrowLast && i == Items.Count - 1))
cd.Width = new GridLength(1, GridUnitType.Star);
else
cd.Width = new GridLength(1, GridUnitType.Auto);
RootGrid.ColumnDefinitions.Add(cd);
}
RootGrid.RowDefinitions.Add(new RowDefinition());
RootGrid.RowDefinitions.Add(new RowDefinition());
foreach (InfoTableItem item in Items.Cast<InfoTableItem>())
{
int index = Items.IndexOf(item);
RootGrid.Children.Add(BuildHeaderCell(item, index));
RootGrid.Children.Add(BuildDataCell(item, index));
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (isTemplateApplied)
return;
isTemplateApplied = true;
}
private Border BuildHeaderCell(InfoTableItem item, int index)
{
Border b = new Border { Style = HeaderCellStyle };
TextBlock t = new TextBlock { Style = HeaderTextStyle, Text = item.Header };
b.SetValue(Grid.ColumnProperty, index);
b.Child = t;
return b;
}
private Border BuildDataCell(InfoTableItem item, int index)
{
Border b = new Border { Style = DataCellStyle };
TextBlock t = new TextBlock { Style = DataTextStyle };
Binding binding = new Binding { Source = Items[index], Path = new PropertyPath("Data"), UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged };
t.SetBinding(TextBlock.TextProperty, binding);
b.SetValue(Grid.ColumnProperty, index);
b.SetValue(Grid.RowProperty, 1);
b.Child = t;
return b;
}
}
}
Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:InfoTableHost">
<Style TargetType="{x:Type local:InfoTable}">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="HeaderCellStyle">
<Setter.Value>
<Style TargetType="Border">
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="4" />
<Setter Property="Background" Value="Gray"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value=".5,.5,.5,.5"/>
</Style>
</Setter.Value>
</Setter>
<Setter Property="HeaderTextStyle">
<Setter.Value>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White"/>
</Style>
</Setter.Value>
</Setter>
<Setter Property="DataCellStyle">
<Setter.Value>
<Style TargetType="Border">
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="4" />
<Setter Property="Background" Value="DarkGray"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value=".5,0,.5,.5"/>
</Style>
</Setter.Value>
</Setter>
<Setter Property="DataTextStyle">
<Setter.Value>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White"/>
</Style>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:InfoTable}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalAlignment}">
<Grid VerticalAlignment="Top" x:Name="root" Margin="0">
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
MainWindow.xaml (Host page)
<Window x:Class="InfoTableHost.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:InfoTableHost"
Title="MainWindow" Height="350" Width="525" Padding="0">
<StackPanel DataContext="{Binding Observation}" >
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="18"/>
<Setter Property="Foreground" Value="Black"/>
</Style>
</StackPanel.Resources>
<local:InfoTable>
<local:InfoTableItem Header="Unbound Header" Data="Unbound Data"/>
<local:InfoTableItem Header="Observation Date" Data="{Binding ObSDate}"/>
<local:InfoTableItem Header="Close" Data="{Binding Close}"/>
</local:InfoTable>
<TextBlock>This TextBlock Binding Works: <TextBlock Text="{Binding ObsDate}" /></TextBlock>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace InfoTableHost
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public Observation Observation { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
Observation = new Observation { ObsDate = DateTime.Now, Close = 2201.55m };
}
}
public class Observation : INotifyPropertyChanged
{
private DateTime _ObsDate;
public DateTime ObsDate
{
get { return _ObsDate; }
set
{
if (_ObsDate != value)
{
_ObsDate = value;
RaisePropertyChanged();
}
}
}
private decimal _Close;
public decimal Close
{
get { return _Close; }
set
{
if (_Close != value)
{
_Close = value;
RaisePropertyChanged();
}
}
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberNameAttribute] string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
Upvotes: 1
Views: 141
Reputation:
It turns out that the problem is with the InfoTableItem class. Apparently DependencyObject does not inherit the DataContext of the parent control so the binding has no DataContext. To resolve this, make InfoTableItem inherit from FrameworkElement and you are in business:
public class InfoTableItem : FrameworkElement
{
...
}
Upvotes: 1