How to use ColumnDefinitions.Add correctly? - wpf

I copied an example about dock panel from "WPF 4.5 Unleashed" ch.5. In the example the dock is set to be in the right of the grid So when in the code behind layer0.ColumnDefinitions.Add(column1CloneForLayer0); called, a column with content column1CloneForLayer0 will be created in the right side of the grid.
In my project I almost did the same thing, but when I call the function to add a column with some content, some empty space's created at the right side.. which is not as expected.So what's the right thing to do?
1.How to Add a column in the left side of a grid?
2.In the example the column was created yet why it's empty? Maybe Do i need to set the Z Index?
Update
I added something similar in the original example in the book and get wrong result. Below is the the source codes. I just added a leftToolBox in the left.
https://www.dropbox.com/sh/61hm139j77kz9k1/AACKqhG5uXFkQgnt8fWi4NvNa?dl=0
Update2
Relevant codes: In this case I just add a stackpanel with button in the left side and and click to call DocakPane(3), instead of giving the new column in the left, it creates column in the right.
public void DockPane(int paneNumber)
{
if (paneNumber == 1)
{
pane1Button.Visibility = Visibility.Collapsed;
pane1PinImage.Source = new BitmapImage(new Uri("pin.gif", UriKind.Relative));
// Add the cloned column to layer 0:
layer0.ColumnDefinitions.Add(column1CloneForLayer0);
// Add the cloned column to layer 1, but only if pane 2 is docked:
if (pane2Button.Visibility == Visibility.Collapsed) layer1.ColumnDefinitions.Add(column2CloneForLayer1);
}
else if (paneNumber == 2)
{
pane2Button.Visibility = Visibility.Collapsed;
pane2PinImage.Source = new BitmapImage(new Uri("pin.gif", UriKind.Relative));
// Add the cloned column to layer 0:
layer0.ColumnDefinitions.Add(column2CloneForLayer0);
// Add the cloned column to layer 1, but only if pane 1 is docked:
if (pane1Button.Visibility == Visibility.Collapsed) layer1.ColumnDefinitions.Add(column2CloneForLayer1);
}
else
{
leftpane1Button.Visibility = Visibility.Collapsed;
pane3PinImage.Source = new BitmapImage(new Uri("pin.gif", UriKind.Relative));
layer0.ColumnDefinitions.Add(testcol);
}
}

Based on the sample project provided, here is a re-write of the same using MVVM and most of the problem related to hard-coding would are gone. It is not pure MVVM but it is mostly MVVM to get started with.
start by defining ViewModels
a class to represent the dock pane
class DockablePaneVM : ViewModelBase
{
public DockablePaneVM(DockHostVM host)
{
Host = host;
}
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
RaisePropertyChanged("Title");
}
}
private bool _isPinned;
public bool IsPinned
{
get { return _isPinned; }
set
{
_isPinned = value;
Host.PinModeChanged(this);
RaisePropertyChanged("IsPinned");
}
}
private object _content;
public object Content
{
get { return _content; }
set
{
_content = value;
RaisePropertyChanged("Content");
}
}
public DockHostVM Host { get; private set; }
public Dock Dock { get; set; }
}
host for the dockpanes, I have used collectionview for simplifying the location
class DockHostVM : ViewModelBase
{
public DockHostVM()
{
Panes = new ObservableCollection<DockablePaneVM>();
LeftPanes = new CollectionViewSource() { Source = Panes }.View;
RightPanes = new CollectionViewSource() { Source = Panes }.View;
FlotingLeftPanes = new CollectionViewSource() { Source = Panes }.View;
FlotingRightPanes = new CollectionViewSource() { Source = Panes }.View;
LeftPanes.Filter = o => Filter(o, Dock.Left, true);
RightPanes.Filter = o => Filter(o, Dock.Right, true);
FlotingLeftPanes.Filter = o => Filter(o, Dock.Left, false);
FlotingRightPanes.Filter = o => Filter(o, Dock.Right, false);
}
private bool Filter(object obj, Dock dock, bool isPinned)
{
DockablePaneVM vm = obj as DockablePaneVM;
return vm.Dock == dock && vm.IsPinned == isPinned;
}
public ObservableCollection<DockablePaneVM> Panes { get; set; }
public ICollectionView LeftPanes { get; set; }
public ICollectionView RightPanes { get; set; }
public ICollectionView FlotingLeftPanes { get; set; }
public ICollectionView FlotingRightPanes { get; set; }
private object _content;
public object Content
{
get { return _content; }
set { _content = value; }
}
public void PinModeChanged(DockablePaneVM paneVM)
{
LeftPanes.Refresh();
RightPanes.Refresh();
FlotingLeftPanes.Refresh();
FlotingRightPanes.Refresh();
}
//sample generator
public static DockHostVM GetSample()
{
DockHostVM vm = new DockHostVM();
vm.Panes.Add(new DockablePaneVM(vm) { Title = "Left Toolbox", Content = new ToolBoxVM() });
vm.Panes.Add(new DockablePaneVM(vm) { Title = "Solution Explorer", Content = new SolutionExplorerVM(), Dock = Dock.Right });
vm.Panes.Add(new DockablePaneVM(vm) { Title = "Toolbox", Content = new ToolBoxVM(), Dock = Dock.Right });
return vm;
}
}
then styles, to give provide the view for the classes
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:VisualStudioLikePanes">
<DataTemplate DataType="{x:Type l:DockablePaneVM}">
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBlock Padding="8,4"
Text="{Binding Title}"
TextTrimming="CharacterEllipsis"
Background="{DynamicResource {x:Static SystemColors.ActiveCaptionBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.ActiveCaptionTextBrushKey}}" />
<ToggleButton IsChecked="{Binding IsPinned}"
Grid.Column="1">
<Image Name="pinImage"
Source="pinHorizontal.gif" />
</ToggleButton>
<ContentControl Content="{Binding Content}"
Grid.Row="1"
Grid.ColumnSpan="2" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsPinned}"
Value="True">
<Setter Property="Source"
TargetName="pinImage"
Value="pin.gif" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<Style TargetType="TabItem">
<Setter Property="Header"
Value="{Binding Title}" />
</Style>
<Style TargetType="TabItem"
x:Key="FloterItem"
BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="90" />
</Setter.Value>
</Setter>
</Style>
<Style TargetType="TabControl">
<Setter Property="l:TabControlHelper.IsLastItemSelected"
Value="True" />
<Style.Triggers>
<Trigger Property="HasItems"
Value="false">
<Setter Property="Visibility"
Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="TabControl"
x:Key="AutoResizePane"
BasedOn="{StaticResource {x:Type TabControl}}">
<Style.Triggers>
<Trigger Property="IsMouseOver"
Value="false">
<Setter Property="Width"
Value="23" />
</Trigger>
</Style.Triggers>
</Style>
<DataTemplate DataType="{x:Type l:ToolBoxVM}">
<ListBox Padding="10"
Grid.Row="1">
<ListBoxItem>Button</ListBoxItem>
<ListBoxItem>CheckBox</ListBoxItem>
<ListBoxItem>ComboBox</ListBoxItem>
<ListBoxItem>Label</ListBoxItem>
<ListBoxItem>ListBox</ListBoxItem>
</ListBox>
</DataTemplate>
<DataTemplate DataType="{x:Type l:SolutionExplorerVM}">
<TreeView Grid.Row="2">
<TreeViewItem Header="My Solution" IsExpanded="True">
<TreeViewItem Header="Project #1" />
<TreeViewItem Header="Project #2" />
<TreeViewItem Header="Project #3" />
</TreeViewItem>
</TreeView>
</DataTemplate>
</ResourceDictionary>
I have also created some dummy classes to represent the toolbox and solution explorer
also a helper class to improve the usability of tab control which i have used to host the dockpanes
class TabControlHelper
{
public static bool GetIsLastItemSelected(DependencyObject obj)
{
return (bool)obj.GetValue(IsLastItemSelectedProperty);
}
public static void SetIsLastItemSelected(DependencyObject obj, bool value)
{
obj.SetValue(IsLastItemSelectedProperty, value);
}
// Using a DependencyProperty as the backing store for IsLastItemSelected. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsLastItemSelectedProperty =
DependencyProperty.RegisterAttached("IsLastItemSelected", typeof(bool), typeof(TabControlHelper), new PropertyMetadata(false, OnIsLastItemSelected));
private static void OnIsLastItemSelected(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TabControl tc = d as TabControl;
tc.Items.CurrentChanged += (ss, ee) =>
{
if (tc.SelectedIndex < 0 && tc.Items.Count > 0)
tc.SelectedIndex = 0;
};
}
}
this will keep an item selected any time, in this project it will be used when a dock pane is pinned/unpinned
now the main window, note that I have bounded the dock Panes to 4 tab controls, left, right, left float & right float
<Window x:Class="VisualStudioLikePanes.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="500"
Width="800"
WindowStartupLocation="CenterScreen"
xmlns:l="clr-namespace:VisualStudioLikePanes">
<Window.DataContext>
<ObjectDataProvider MethodName="GetSample"
ObjectType="{x:Type l:DockHostVM}" />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Menu>
<MenuItem Header="FILE" />
...
<MenuItem Header="HELP" />
</Menu>
<DockPanel LastChildFill="True"
Grid.Row="1">
<Border Width="23"
DockPanel.Dock="Left"
Visibility="{Binding Visibility, ElementName=LeftFloter}" />
<Border Width="23"
DockPanel.Dock="Right"
Visibility="{Binding Visibility, ElementName=RightFloter}" />
<TabControl ItemsSource="{Binding LeftPanes}"
DockPanel.Dock="Left" />
<TabControl ItemsSource="{Binding RightPanes}"
DockPanel.Dock="Right" />
<Grid Name="layer0">
... page content
</Grid>
</DockPanel>
<TabControl ItemsSource="{Binding FlotingLeftPanes}"
Grid.Row="1"
HorizontalAlignment="Left"
TabStripPlacement="Left"
Style="{StaticResource AutoResizePane}"
ItemContainerStyle="{StaticResource FloterItem}"
x:Name="LeftFloter" />
<TabControl ItemsSource="{Binding FlotingRightPanes}"
Grid.Row="1"
HorizontalAlignment="Right"
TabStripPlacement="Right"
Style="{StaticResource AutoResizePane}"
ItemContainerStyle="{StaticResource FloterItem}"
x:Name="RightFloter" />
</Grid>
</Window>
result is your expected behavior with MVVM approach, adding new panel is easy as Panes.Add(new DockablePaneVM(vm) { Title = "Left Toolbox", Content = new ToolBoxVM() }); rest is handled.
Demo
download the working sample VisualStudioLikePanes.zip

Related

PropertyGroupDescription CustomSort is missing the ItemCount in item X

Preamble:
First I have searched stackoverflow and all of the topics pertaining to sorting grouped datagrids do not apply to this question. In fact none of the answers actually show how to sort by group count without using 3rd partly libraries.
The Problem:
I am trying to sort my datagrid groups by count by overriding the CustomSort property of the PropertyGroupDescription. When I assign a CustomSort method to the GroupDescription, the Compare function's object x CollectionViewGroup always has an ItemCount == 0.
Here is my sample xaml which is mostly taken from Microsoft's help:
<Window
x:Class="GroupedSorting.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:GroupedSorting"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<DataGrid
Name="dg"
Grid.Row="0"
ItemsSource="{Binding ItemVMs}">
<DataGrid.GroupStyle>
<!-- Style for groups at top level. -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="#FF112255" BorderBrush="#FF002255" Foreground="#FFEEEEEE" BorderThickness="1,1,1,5">
<Expander.Header>
<DockPanel>
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5,0,0,0" Width="100"/>
<TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}"/>
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
<!-- Style for groups under the top level. -->
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<DockPanel Background="LightBlue">
<TextBlock Text="{Binding Path=Name, Converter={StaticResource completeConverter}}" Foreground="Blue" Margin="30,0,0,0" Width="100"/>
<TextBlock Text="{Binding Path=ItemCount}" Foreground="Blue"/>
</DockPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Foreground" Value="Black" />
<Setter Property="Background" Value="White" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
</Grid>
</Window>
And here is the code behind:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Data;
namespace GroupedSorting
{
public partial class MainWindow : Window
{
public ObservableCollection<MyViewModel> ItemVMs { get; set; } = new ObservableCollection<MyViewModel>();
public MainWindow()
{
InitializeComponent();
DataContext = this;
var r = new Random();
for (int i = 0; i < 10; i++)
{
ItemVMs.Add(new MyViewModel()
{
Name = "Group 1",
MyIndex = r.Next()
}); ;
}
for (int i = 0; i < 5; i++)
{
ItemVMs.Add(new MyViewModel()
{
Name = "Group 2",
MyIndex = r.Next()
});
}
for (int i = 0; i < 1; i++)
{
ItemVMs.Add(new MyViewModel()
{
Name = "Group 3",
MyIndex = r.Next()
});
}
List<MyViewModel> sortedItems = new List<MyViewModel>();
var groups = ItemVMs.GroupBy(x => x.Name);
foreach (var group in groups.OrderByDescending(x => x.Count()))
{
sortedItems.AddRange(group);
}
ItemVMs.Clear();
ItemVMs = new ObservableCollection<MyViewModel>(sortedItems);
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var sourceView = CollectionViewSource.GetDefaultView(dg.ItemsSource) as ListCollectionView;
var gd = new PropertyGroupDescription(nameof(MyViewModel.Name));
gd.CustomSort = new GroupComparer();
sourceView.GroupDescriptions.Add(gd);
sourceView.Refresh();
}
}
public class MyViewModel
{
public string Name { get; set; }
public int MyIndex { get; set; }
}
public class GroupComparer : System.Collections.IComparer
{
public int Compare(object x, object y)
{
if (!(x is CollectionViewGroup xViewGroup))
return 0;
if (!(y is CollectionViewGroup yViewGroup))
return 0;
Debug.WriteLine($"{xViewGroup.Name} {xViewGroup.ItemCount}, {yViewGroup.Name} {yViewGroup.ItemCount}");
if (xViewGroup.ItemCount < yViewGroup.ItemCount)
return 1;
else if (xViewGroup.ItemCount > yViewGroup.ItemCount)
return -1;
return 0;
}
}
}
When the code is run, the xViewGroup.ItemCount is always equal to 0 causing the sort method to fail.
Cast the ICollectionView returned from CollectionViewSource.GetDefaultView to a ListCollectionView and set the CustomSort property of this one to your custom IComparer:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var sourceView = CollectionViewSource.GetDefaultView(dg.ItemsSource) as ListCollectionView;
var gd = new PropertyGroupDescription(nameof(MyViewModel.Name));
sourceView.CustomSort = new GroupComparer();
sourceView.GroupDescriptions.Add(gd);
sourceView.Refresh();
}

Create itemscontrol multiple times with different lists

I'm trying to print 5 albums and for every album print all its' songs with a checkbox near every song. (I recommend you to see the image published below)
I tried to do than with an ItemsControl but I don't know how to do that so every ItemsControl will Bind another list (with a specific album's songs).
I made all the 5 albums within a for loop.
My problems are:
For every album how do I create an ItemsControl for its' specific songs'
list.
Every time I check a CheckBox it checks all the checkboxes in its'
row (all the other albums).
Here is the code of a single ItemsControl:
<ItemsControl Grid.Column="1" ItemsSource="{Binding}"
Grid.IsSharedSizeScope="True"
Margin="12 0 12 0">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type domain:SelectableViewModel}">
<Border x:Name="Border" Padding="8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Checkerz" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox VerticalAlignment="Center" IsChecked="{Binding IsSelected}"/>
<StackPanel Margin="8 0 0 0" Grid.Column="1">
<TextBlock FontWeight="Bold" Text="{Binding Name}" />
<TextBlock Text="{Binding Description}" />
</StackPanel>
</Grid>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="Border" Property="Background" Value="{DynamicResource MaterialDesignSelection}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is an image of how it's looks like now. (look at the red squares):
Click here
Please help me :(
try the next solution:
For every album how do I create an ItemsControl for its' specific songs' list.
Udate #1 - Xaml code solution
<Window x:Class="ListBoxOflistboxes.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:listBoxOflistboxes="clr-namespace:ListBoxOflistboxes"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="InnerItemsControl" DataType="{x:Type listBoxOflistboxes:AlbumViewModel}">
<ItemsControl Grid.Column="1" ItemsSource="{Binding Songs}"
Grid.IsSharedSizeScope="True"
Margin="12 0 12 0">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type listBoxOflistboxes:SelectableViewModel}">
<Border x:Name="Border" Padding="8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Checkerz" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox VerticalAlignment="Center" IsChecked="{Binding IsSelected}"/>
<StackPanel Margin="8 0 0 0" Grid.Column="1">
<TextBlock FontWeight="Bold" Text="{Binding Name}" />
<TextBlock Text="{Binding Description}" />
</StackPanel>
</Grid>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="Border" Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding IsSelected}" Value="False">
<Setter TargetName="Border" Property="Background" Value="Transparent" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
<DataTemplate x:Key="ListBoxItemDataTemplate" DataType="listBoxOflistboxes:AlbumViewModel">
<ContentControl Content="{Binding }" ContentTemplate="{StaticResource InnerItemsControl}"></ContentControl>
</DataTemplate>
</Grid.Resources>
<Grid.DataContext>
<listBoxOflistboxes:MainAlbumsViewModel/>
</Grid.DataContext>
<ListBox ItemsSource="{Binding Albums}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid Margin="1">
<Border x:Name="MouseOverBorder" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Background="LightGreen" BorderBrush="DimGray" BorderThickness="1.5" Visibility="Collapsed"/>
<Border x:Name="SelectedBorder" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Background="Green" BorderBrush="Black" BorderThickness="1.5" Visibility="Collapsed"/>
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{StaticResource ListBoxItemDataTemplate}" />
</Grid>
<ControlTemplate.Triggers>
<!--Uncomment these trigger in order to make the mouse over border to be visible-->
<!--<Trigger Property="ListBoxItem.IsMouseOver" Value="True">
<Setter TargetName="MouseOverBorder" Property="Visibility" Value="Visible"/>
</Trigger>
<Trigger Property="ListBoxItem.IsMouseOver" Value="False">
<Setter TargetName="MouseOverBorder" Property="Visibility" Value="Collapsed"/>
</Trigger>-->
<Trigger Property="ListBoxItem.IsSelected" Value="True">
<Setter TargetName="SelectedBorder" Property="Visibility" Value="Visible"/>
</Trigger>
<Trigger Property="ListBoxItem.IsSelected" Value="False">
<Setter TargetName="SelectedBorder" Property="Visibility" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
Every time I check a CheckBox it checks all the checkboxes in its' row (all the other albums).
Managed in view-models using shared models
/// <summary>
/// main view model
/// </summary>
public class MainAlbumsViewModel:BaseObservableObject
{
private readonly ISelectionEventAggregator _selectionEventAggregator;
private readonly ISongsProvider _songsProvider;
private ObservableCollection<AlbumViewModel> _albums;
public MainAlbumsViewModel()
{
_selectionEventAggregator = new SelectionEventAggregator();
_songsProvider = new SongsProvider();
Albums = new ObservableCollection<AlbumViewModel>
{
new AlbumViewModel(_selectionEventAggregator, _songsProvider),
new AlbumViewModel(_selectionEventAggregator, _songsProvider),
new AlbumViewModel(_selectionEventAggregator, _songsProvider),
};
Albums.ToList().ForEach(model =>
{
_songsProvider.RegisterSongs(model, new List<SelectableViewModel>
{
new SelectableViewModel(_selectionEventAggregator){Name = "Song A", Description = "Description A"},
new SelectableViewModel(_selectionEventAggregator){Name = "Song B", Description = "Description B"},
new SelectableViewModel(_selectionEventAggregator){Name = "Song C", Description = "Description C"},
new SelectableViewModel(_selectionEventAggregator){Name = "Song D", Description = "Description D"},
});
});
}
public ObservableCollection<AlbumViewModel> Albums
{
get { return _albums; }
set
{
_albums = value;
OnPropertyChanged();
}
}
}
/// <summary>
/// an album view-model
/// </summary>
public class AlbumViewModel:BaseObservableObject
{
public AlbumViewModel(ISelectionEventAggregator selectionEventAggregator, ISongsProvider songsProvider)
{
_selectionEventAgreggator = selectionEventAggregator;
_songsProvider = songsProvider;
//you should think about an unsubscribe mechanism
_selectionEventAgreggator.SelectionEventHandler += SelectionEventAgreggatorOnSelectionEventHandler;
}
private void SelectionEventAgreggatorOnSelectionEventHandler(object sender, SelectionEventArgs args)
{
var key = args.Key as SelectableViewModel;
if(key == null) return;
var existingSong = Songs.FirstOrDefault(model => model.Name.Equals(key.Name) && model.Description.Equals(key.Description));
if(existingSong == null) return;
existingSong.UpdateSelectionSilentely(args.IsSelected);
}
private ObservableCollection<SelectableViewModel> _songs;
private readonly ISelectionEventAggregator _selectionEventAgreggator;
private readonly ISongsProvider _songsProvider;
public ObservableCollection<SelectableViewModel> Songs
{
get { return _songs ?? (_songs = new ObservableCollection<SelectableViewModel>(_songsProvider.GetSongs(this))); }
}
}
/// <summary>
/// helps to provide songs
/// </summary>
public interface ISongsProvider
{
List<SelectableViewModel> GetSongs(object albumKey);
void RegisterSongs(object albumKey, IEnumerable<SelectableViewModel> songs);
}
class SongsProvider : ISongsProvider
{
private Dictionary<object, List<SelectableViewModel>> _albums = new Dictionary<object, List<SelectableViewModel>>();
public List<SelectableViewModel> GetSongs(object albumKey)
{
return _albums.ContainsKey(albumKey) == false ? null : _albums[albumKey];
}
public void RegisterSongs(object albumKey, IEnumerable<SelectableViewModel> songs)
{
if (_albums.ContainsKey(albumKey) == false)
{
if(songs == null) return;
_albums.Add(albumKey, songs.ToList());
}
else
{
if (songs == null)
{
_albums.Remove(albumKey);
return;
}
_albums[albumKey] = songs.ToList();
}
}
}
/// <summary>
/// a single song view-model
/// </summary>
public class SelectableViewModel:BaseObservableObject
{
private readonly ISelectionEventAggregator _selectionEventAggregator;
private bool _isSelected;
private string _name;
private string _description;
public SelectableViewModel(ISelectionEventAggregator selectionEventAggregator)
{
_selectionEventAggregator = selectionEventAggregator;
}
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged();
_selectionEventAggregator.Publish(new SelectionEventArgs {Key = this, IsSelected = _isSelected});
}
}
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public string Description
{
get { return _description; }
set
{
_description = value;
OnPropertyChanged();
}
}
public void UpdateSelectionSilentely(bool isSelected)
{
_isSelected = isSelected;
OnPropertyChanged(() => IsSelected);
}
}
/// <summary>
/// helps to manage selection
/// </summary>
public interface ISelectionEventAggregator
{
event EventHandler<SelectionEventArgs> SelectionEventHandler;
void Publish(SelectionEventArgs selectionEventArgs);
}
public class SelectionEventAggregator : ISelectionEventAggregator
{
public event EventHandler<SelectionEventArgs> SelectionEventHandler;
public void Publish(SelectionEventArgs selectionEventArgs)
{
OnSelectionEventHandler(selectionEventArgs);
}
protected virtual void OnSelectionEventHandler(SelectionEventArgs e)
{
var handler = SelectionEventHandler;
if (handler != null) handler(this, e);
}
}
public class SelectionEventArgs:EventArgs
{
public object Key { get; set; }
public bool IsSelected { get; set; }
}
BaseObservableObject - simple INPC
/// <summary>
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
Explanation:
The main idea for the second part of your question is to use some kind of an event aggregation.
How it looks like
Regards.

XAML to add header to radio button

So with a lot of looking around I am hoping to make a GroupBox that acts like a Radio button. The header section would act as the bullet. I took some code from this question
Styling a GroupBox
that is how I want it to look. But I want to have it as a Radio button. So I put in this code (mind you I've only been doing WPF for a week or 2 now)
<Style TargetType="{x:Type RadioButton}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RadioButton}">
<BulletDecorator>
<BulletDecorator.Bullet>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border x:Name="SelectedBorder"
Grid.Row="0"
Margin="4"
BorderBrush="Black"
BorderThickness="1"
Background="#25A0DA">
<Label x:Name="SelectedLabel" Foreground="Wheat">
<ContentPresenter Margin="4" />
</Label>
</Border>
<Border>
</Border>
</Grid>
</BulletDecorator.Bullet>
</BulletDecorator>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter TargetName="SelectedBorder" Property="Background" Value="PaleGreen"/>
<Setter TargetName="SelectedLabel"
Property="Foreground"
Value="Black" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have a feeling that I can add a label to the second row of my grid, but then I don't know how to access it. I have that template in a test project in the Window.Resources section (I plan on moving it to a resource dictionary in my main project)
the xaml for my window is this
<Grid>
<GroupBox Name="grpDoor" Margin ="8" Grid.Row="0" Grid.Column="0">
<GroupBox.Header>
WPF RadioButton Template
</GroupBox.Header>
<StackPanel Margin ="8">
<RadioButton FontSize="15" Content="Dhaka" Margin="4" IsChecked="False"/>
<RadioButton FontSize="15" Content="Munshiganj" Margin="4" IsChecked="True" />
<RadioButton FontSize="15" Content="Gazipur" Margin="4" IsChecked="False" />
</StackPanel>
</GroupBox>
</Grid>
I then hoping for something like this (not sure how I'd do it yet though)
<Grid>
<GroupBox Name="grpDoor" Margin ="8" Grid.Row="0" Grid.Column="0">
<GroupBox.Header>
WPF RadioButton Template
</GroupBox.Header>
<StackPanel Margin ="8">
<RadioButton FontSize="15"
Content="Dhaka"
Margin="4"
IsChecked="False">
<RadioButton.Description>
This is a description that would show under my Header
</RadioButton.Description>
</RadioButton>
<RadioButton FontSize="15"
Content="Munshiganj"
Margin="4"
IsChecked="True">
<RadioButton.Description>
This is a description that would show under my Header
</RadioButton.Description>
</RadioButton>
<RadioButton FontSize="15"
Content="Gazipur"
Margin="4"
IsChecked="False">
<RadioButton.Description>
This is a description that would show under my Header
</RadioButton.Description>
</RadioButton>
</StackPanel>
</GroupBox>
</Grid>
Based on your clarification, here is a very simple example with a RadioButton that looks like a GroupBox.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:SimpleViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type local:SimpleOption}">
<RadioButton GroupName="choice" IsChecked="{Binding Path=IsSelected, Mode=TwoWay}">
<RadioButton.Template>
<ControlTemplate TargetType="{x:Type RadioButton}">
<GroupBox x:Name="OptionBox" Header="{Binding Path=DisplayName, Mode=OneWay}">
<TextBlock Text="{Binding Path=Description, Mode=OneWay}"/>
</GroupBox>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected, Mode=OneWay}" Value="True">
<Setter TargetName="OptionBox" Property="Background" Value="Blue"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=Options, Mode=OneWay}"/>
</Grid>
</Window>
public class SimpleViewModel
{
public SimpleViewModel()
{
Options = new ObservableCollection<SimpleOption>();
var _with1 = Options;
_with1.Add(new SimpleOption {
DisplayName = "Dhaka",
Description = "This is a description for Dhaka."
});
_with1.Add(new SimpleOption {
DisplayName = "Munshiganj",
Description = "This is a description for Munshiganj.",
IsSelected = true
});
_with1.Add(new SimpleOption {
DisplayName = "Gazipur",
Description = "This is a description for Gazipur."
});
}
public ObservableCollection<SimpleOption> Options { get; set; }
}
public class SimpleOption : INotifyPropertyChanged
{
public string DisplayName {
get { return _displayName; }
set {
_displayName = value;
NotifyPropertyChanged("DisplayName");
}
}
private string _displayName;
public string Description {
get { return _description; }
set {
_description = value;
NotifyPropertyChanged("Description");
}
}
private string _description;
public bool IsSelected {
get { return _isSelected; }
set {
_isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
private bool _isSelected;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged;
public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);
}
I'd do it with a custom attached property. That way, you can bind to it from a ViewModel, or apply it directly in XAML.
First, create a new class in your Style assembly:
public static class RadioButtonExtender
{
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.RegisterAttached(
"Description",
typeof(string),
typeof(RadioButtonExtender),
new FrameworkPropertyMetadata(null));
[AttachedPropertyBrowsableForType(typeof(RadioButton))]
public static string GetDescription(RadioButton obj)
{
return (string)obj.GetValue(DescriptionProperty);
}
public static void SetDescription(RadioButton obj, string value)
{
obj.SetValue(DescriptionProperty, value);
}
}
And your style's Bullet would change so that the label is:
<Label x:Name="SelectedLabel"
Foreground="Wheat"
Content="{Binding (prop:RadioButtonExtender.Description), RelativeSource={RelativeSource TemplatedParent}} />
You could then use it in your final XAML:
<RadioButton FontSize="15"
Content="Dhaka"
Margin="4"
IsChecked="False">
<prop:RadioButtonExtender.Description>
This is a description that would show under my Header
</prop:RadioButtonExtender.Description>
</RadioButton>
As an added bonus, since you're creating the Style in a separate assembly, you can create a custom XAML namespace to make using your property easier.

Data binding to a TreeView using HierarchicalDataTemplate

I'm trying to get the TreeView control in my application to properly bind to a tree of objects by setting its ItemsSource and DataContext properties. The tree visualizes as expected, but the TreeViewItems data context seems to hold incorrect values.
For example, I have a tree that looks like this:
[-] Header
[-] Contents
[+] Item1
[+] Item2
properties
[+] Dictionary
[-] MetaDictionary
[+] TypeDef1
[+] TypeDef2
properties
The items are bound to the objects' Data.Name value. However, if I click any item that is a child of Header and examine it in the event handler, its DataContext.Data.Name says Header (after appropriate castings). Same thing happens with MetaDictionary and its children.
This is a shortened version of my class:
public class CFItemTreeNode
{
private CFItem data;
public CFItem Data
{
get { return data; }
set { data = value; }
}
private ObservableCollection<CFItemTreeNode> children;
public ObservableCollection<CFItemTreeNode> Children
{
//set & get as above
}
private CFItemTreeNode parent;
public CFItemTreeNode Parent
{
//set & get as above
}
}
And this is my XAML. I've been scouring SO and the net for several days and I've incorporated bits and pieces of various tutorials and questions into this Frankenstein of mine. I believe it's a problem with the hierarchical template, but that's as far as I've gotten.
<Window x:Class="SSLowLevelBrowser.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SSLowLevelBrowser"
Title="MainWindow"
Height="600"
Width="800"
MinHeight="100"
MinWidth="200"
Closing="MainWindowClosing">
<Window.Resources>
<Style x:Key="TreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
<!-- A margin of 0 px left and 2 px top -->
<Setter Property="Margin" Value="0 2" />
<Setter Property="AllowDrop" Value="true" />
<EventSetter Event="TreeViewItem.PreviewMouseLeftButtonDown" Handler="TVI_PreviewMouseLeftButtonDown" />
<EventSetter Event="TreeViewItem.PreviewMouseMove" Handler="TVI_PreviewMouseMove" />
<EventSetter Event="TreeViewItem.PreviewDrop" Handler="TVI_PreviewDrop" />
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="575*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="390*" />
<ColumnDefinition Width="390*" />
</Grid.ColumnDefinitions>
<ToolBar Name="menuBar" Grid.ColumnSpan="2" ToolBarTray.IsLocked="True">
<Button Name="buttonOpen" Click="OpenFile">Open file</Button>
</ToolBar>
<TreeView Grid.Row="1"
Grid.Column="0"
Name="treeView"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
ItemsSource="{Binding}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:CFItemTreeNode}" ItemsSource="{Binding Children}">
<Grid>
<TextBlock Text="{Binding Path=Data.Name}"
MouseLeftButtonDown="TBlock_PreviewMouseLeftButtonDown"/>
<TextBox Text="{Binding Path=Data.Name, Mode=TwoWay}"
Visibility="Collapsed"
LostFocus="TBox_LostFocus"/>
</Grid>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
<TextBox Grid.Row="1" Grid.Column="1" Name="textOutput" />
</Grid>
</Window>
What am I doing wrong?
Update 1. Here are my event handlers:
private void TVI_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs args)
{
dragStartPosition = args.GetPosition(null);
dragSource = args.OriginalSource;
}
private void TVI_PreviewMouseMove(object sender, MouseEventArgs args)
{
Point currentPosition = args.GetPosition(null);
// If there is actual movement and a drag is starting
if (dragInProgress == false &&
dragStartPosition.X != -1 &&
args.LeftButton == MouseButtonState.Pressed &&
Math.Pow(currentPosition.X - dragStartPosition.X, 2) +
Math.Pow(currentPosition.Y - dragStartPosition.Y, 2) > 25)
{
dragInProgress = true;
DragDropEffects de = DragDrop.DoDragDrop(
(TreeViewItem)sender,
new DataObject(typeof(FrameworkElement), dragSource),
DragDropEffects.Move);
}
}
private void TVI_PreviewDrop(object sender, DragEventArgs args)
{
if (dragInProgress && args.Data.GetDataPresent(typeof(FrameworkElement)))
{
CFItemTreeNode dragSource =
((CFItemTreeNode)((FrameworkElement)args.Data.GetData(typeof(FrameworkElement))).DataContext);
CFItemTreeNode dropTarget =
((CFItemTreeNode)((FrameworkElement)args.OriginalSource).DataContext);
CFItemTreeNode sourceParent = dragSource.Parent;
CFItemTreeNode targetParent = dropTarget.Parent;
if (sourceParent != targetParent)
{
MessageBox.Show("Can only swap siblings.");
dragInProgress = false;
return;
}
int sourceIndex = sourceParent.Children.IndexOf(dragSource);
int targetIndex = sourceParent.Children.IndexOf(dropTarget);
if (sourceIndex != targetIndex)
{
if (sourceIndex < targetIndex)
{
sourceParent.Children.RemoveAt(targetIndex);
sourceParent.Children.RemoveAt(sourceIndex);
sourceParent.Children.Insert(sourceIndex, dropTarget);
sourceParent.Children.Insert(targetIndex, dragSource);
}
else
{
sourceParent.Children.RemoveAt(sourceIndex);
sourceParent.Children.RemoveAt(targetIndex);
sourceParent.Children.Insert(targetIndex, dragSource);
sourceParent.Children.Insert(sourceIndex, dropTarget);
}
}
dragSource = null;
dragInProgress = false;
// Reset start position to invalid
dragStartPosition = new Point(-1, -1);
}
}
Add RelativeSource={RelativeSource AncestorType=local:MainWindow} to your binding.

WPF DataGrid: ColumnHeaderStyle ContentTemplate is not shown in full height until after resizing

This might be a bug in the WPF Toolkit DataGrid.
In my Windows.Resources I define the following ColumnHeaderStyle:
<Style x:Name="ColumnStyle" x:Key="ColumnHeaderStyle" TargetType="my:DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Data}" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Because my columns are generated dynamically, I am defining the columns in code:
private void CreateColumn(Output output, int index)
{
Binding textBinding = new Binding(string.Format("Relationships[{0}].Formula", index));
DataGridTextColumn tc = new DataGridTextColumn();
tc.Binding = textBinding;
dg.Columns.Add(tc);
tc.Header = output;
}
where Output is a simple class with Name and Data (string) properties.
What I observe is that only the Name property (first TextBlock control in the ContentTemplate's StackPanel) is shown. When I drag one of these column headers, I see the entire header (including the Data TextBlock). Only after manually resizing one of the columns are the column headers rendered correctly. Is there a way to get the column headers to show up correctly in code?
Update: as requested, here is the rest of my code for the repro.
public class Input
{
public Input()
{
Relationships = new ObservableCollection<Relationship>();
}
public string Name { get; set; }
public string Data { get; set; }
public ObservableCollection<Relationship> Relationships { get; set; }
}
public class Output
{
public Output() { }
public string Name { get; set; }
public string Data { get; set; }
}
public class Relationship
{
public Relationship() { }
public string Formula { get; set; }
}
Here is the XAML markup:
<Window x:Class="GridTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300" xmlns:my="http://schemas.microsoft.com/wpf/2008/toolkit">
<Window.Resources>
<SolidColorBrush x:Key="RowHeaderIsMouseOverBrush" Color="Red" />
<SolidColorBrush x:Key="RowBackgroundSelectedBrush" Color="Yellow" />
<BooleanToVisibilityConverter x:Key="bool2VisibilityConverter" />
<Style x:Key="RowHeaderGripperStyle" TargetType="{x:Type Thumb}">
<Setter Property="Height" Value="2"/>
<Setter Property="Background" Value="Green"/>
<Setter Property="Cursor" Value="SizeNS"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Name="ColumnStyle" x:Key="ColumnHeaderStyle" TargetType="my:DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Data}" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- from http://www.codeplex.com/wpf/WorkItem/View.aspx?WorkItemId=9193 -->
<Style x:Name="RowHeaderStyle" x:Key="RowHeaderStyle" TargetType="my:DataGridRowHeader">
<Setter Property="Content" Value="{Binding}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Content.Name, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type my:DataGridRowHeader}}}"
VerticalAlignment="Center"/>
<TextBlock Padding="5">|</TextBlock>
<TextBlock Text="{Binding Path=Content.Data, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type my:DataGridRowHeader}}}"
VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="CellTemplate">
<StackPanel>
<TextBox Text="{Binding Formula, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="CellEditTemplate">
<StackPanel>
<TextBox Text="{Binding Formula, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<my:DataGrid Name="dg"
ColumnHeaderStyle="{StaticResource ColumnHeaderStyle}"
RowHeaderStyle="{StaticResource RowHeaderStyle}"
HeadersVisibility="All" />
</Grid>
</Window>
And finally the code-behind:
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Inputs = new List<Input>();
Outputs = new List<Output>();
Input i1 = new Input() { Name = "I 1", Data = "data 1" };
Input i2 = new Input() { Name = "I 2", Data = "data 2" };
Inputs.Add(i1); Inputs.Add(i2);
Output o1 = new Output() { Name = "O 1", Data = "data 1" };
Output o2 = new Output() { Name = "O 2", Data = "data 2" };
Output o3 = new Output() { Name = "O 3", Data = "data 3" };
Outputs.Add(o1); Outputs.Add(o2); Outputs.Add(o3);
Relationship r1 = new Relationship() { Formula = "F1" };
Relationship r2 = new Relationship() { Formula = "F2" };
Relationship r3 = new Relationship() { Formula = "F3" };
Relationship r4 = new Relationship() { Formula = "F4" };
i1.Relationships.Add(r1);
i1.Relationships.Add(r2);
i2.Relationships.Add(r3);
i2.Relationships.Add(r4);
CreateColumn(o1, 0);
CreateColumn(o2, 1);
CreateColumn(o3, 2);
dg.Items.Add(i1);
dg.Items.Add(i2);
dg.ColumnWidth = DataGridLength.SizeToHeader;
}
private void CreateColumn(Output output, int index)
{
Binding textBinding = new Binding(string.Format("Relationships[{0}].Formula", index));
DataGridTextColumn tc = new DataGridTextColumn();
tc.Binding = textBinding;
dg.Columns.Add(tc);
tc.Header = output;
}
private List<Output> Outputs { get; set; }
private List<Input> Inputs { get; set; }
}
When using a simple DataGrid with ColumnHeaderStyle="{StaticResource ColumnHeaderStyle}", and adding columns using your CreateColumn method, I'm not able to replicate this: both TextBlocks show up fine (on two separate lines) right away.
Could you paste your full DataGrid declaration, and any other styles you have that it might be using?
Also, what version of wpftoolkit are you using? I tested both with the June release and with the DataGrid included in .NET 4 Beta 2.
The issue is fixed if the columns are created in the Loaded event handler, rather than in the constructor of the window.

Resources