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.
Related
Totally i am new to WPF, I need to solve the problem. Could anybody give me a sample xaml code without using code-behind.
Based on the questType (Check,radio, combo) Control need to be created based on the ObserverableCollection.
public class Order
{
public int OrderCode {get;set;}
public string Description {get;set;}
public ObserverableCollection<question> Questions{get;set;}
}
public class question
{
public string questType {get;set;}
public string Question {get;set;}
public ObserverableCollection<Answer> Answers {get;set;}
}
public class Answer
{
public string Ans{get; set;}
}
Based on the questType (Check,radio, combo)
Control need to be created based on the ObserverableCollection.
example:
1001 Pencil Gender? oMale oFemale oOther
[]Checkbox1 []Checkbox2
1002 Pen Fasting? oYes oNo
Here is how I would do it:
Code behind:
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
namespace Ans
{
public class Order
{
public int OrderCode { get; set; }
public string Description { get; set; }
public ObservableCollection<Question> Questions { get; set; }
}
public class Question
{
public string questType { get; set; }
public string Label { get; set; }
public ObservableCollection<Answer> Answers { get; set; }
}
public class Answer
{
public string Ans { get; set; }
public bool IsSelected { get; set; }
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
#region Order
/// <summary>
/// Order Dependency Property
/// </summary>
public static readonly DependencyProperty OrderProperty =
DependencyProperty.Register("Order", typeof(Order), typeof(MainWindow),
new FrameworkPropertyMetadata((Order)null));
/// <summary>
/// Gets or sets the Order property. This dependency property
/// indicates ....
/// </summary>
public Order Order
{
get { return (Order)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
#endregion
public MainWindow()
{
InitializeComponent();
Order = new Order()
{
Questions = new ObservableCollection<Question>()
{
new Question()
{
questType = "Combo",
Label = "Combo",
Answers = new ObservableCollection<Answer>()
{
new Answer(){Ans = "Female"},
new Answer(){Ans = "Male"}
}
},
new Question()
{
questType = "Check",
Label = "Multi",
Answers = new ObservableCollection<Answer>()
{
new Answer(){Ans = "Female"},
new Answer(){Ans = "Male"}
}
},
new Question()
{
questType = "Radio",
Label = "Radio",
Answers = new ObservableCollection<Answer>()
{
new Answer(){Ans = "Female"},
new Answer(){Ans = "Male"}
}
}
}
};
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
foreach(Question q in Order.Questions)
{
Console.WriteLine( q.Label + " : " + string.Join(", " , q.Answers.Where(a=>a.IsSelected).Select(a=>a.Ans)) );
}
}
}
}
XAML:
<Window x:Class="Ans.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:Ans"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate x:Key="ComboQuestion">
<ComboBox ItemsSource="{Binding Answers}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Ans}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="CheckQuestion">
<ItemsControl ItemsSource="{Binding Answers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Ans}" IsChecked="{Binding IsSelected, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
<DataTemplate x:Key="RadioQuestion">
<ItemsControl ItemsSource="{Binding Answers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Ans}" IsChecked="{Binding IsSelected, Mode=TwoWay}" GroupName="{Binding DataContext.Label, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Order.Questions}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Label"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Label}"/>
<ContentControl x:Name="ccQuestion" Grid.Column="1" Content="{Binding}" Margin="10"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding questType}" Value="Combo">
<Setter TargetName="ccQuestion" Property="ContentTemplate" Value="{StaticResource ComboQuestion}"/>
</DataTrigger>
<DataTrigger Binding="{Binding questType}" Value="Check">
<Setter TargetName="ccQuestion" Property="ContentTemplate" Value="{StaticResource CheckQuestion}"/>
</DataTrigger>
<DataTrigger Binding="{Binding questType}" Value="Radio">
<Setter TargetName="ccQuestion" Property="ContentTemplate" Value="{StaticResource RadioQuestion}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Order" Click="Button_Click" VerticalAlignment="Bottom"/>
</Grid>
</Window>
The only thing I've added to your model is IsSelected property that allows to know if this answer was selected.
The other important thing is the Radios. Their GroupName property defines the scope. So if no GroupName is set then when clicking on a radio in one question it will unselect radio in another question. I used Question label in my solution however it only works if labels are unique.
Another point is that data triggers are OK if you have 3-5 question types and if thay are only based on the questionType. However for more complex scenarios you can look for ItemTemplateSelector. It allows to write C# code that will select the template based on each item in ItemsControl.
Allow me to simplify the problem to its basic blocks.
I have a UserControl1 in my project. It has a TextBox like this:
<TextBox Text="{Binding TextProperty}"/>
In the code-behind, I have a dependency property like this:
public string TextProperty
{
get { return (string)GetValue(TextPropertyProperty); }
set { SetValue(TextPropertyProperty, value); }
}
public static readonly DependencyProperty TextPropertyProperty = DependencyProperty.Register("TextProperty", typeof(string), typeof(UserControl1), new PropertyMetadata(null));
And the constructor of the UserControl is simply
public UserControl1()
{
InitializeComponent();
DataContext = this;
}
In the MainWindow, I have this:
<userctrl:UserControl1 TextProperty="{Binding ABC, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="1" Text="{Binding PQR, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
Now, in the viewModel of the MainWindow, I have:
private string _abc;
public string ABC
{
get { return _abc; }
set
{ _abc = "20";
OnPropertyChanged();
}
}
private string _pqr;
public string PQR
{
get { return _pqr; }
set
{
if(value != _pqr)
{
_pqr = value;
ABC = PQR;
OnPropertyChanged();
}
}
}
Now, even the UserControl is supposed to replicate whatever I write in the textbox, right?
But it doesn't. I have set breakpoints at setter of ABC. It gets updated, but that update does not reach the UserControl. My gut says that the binding is failing silently, and it's because I have set the DataContext to this in the constructor of UserControl.
How am I supposed to solve it?
The Actual Scenario:
Here is the XAML for the UserControl:
<UserControl x:Class="MyDiskTools.UserControls.NodeGrid.NodeGrid"
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:MyDiskTools.UserControls.NodeGrid"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<Style TargetType="Button">
<Setter Property="Padding" Value="5"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Command" Value="{Binding InputCommand}"/>
<Setter Property="CommandParameter" Value="{Binding Path=Content, RelativeSource={RelativeSource Self}}"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderThickness" Value="5"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontFamily" Value="Times New Roman"/>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<UniformGrid Grid.Row="0" Rows="1">
<Button Content="A" />
<Button Content="B" />
<Button Content="C" />
<Button Content="D" />
<Button Content="E" />
<Button Content="F" />
</UniformGrid>
<UniformGrid Grid.Row="1" Rows="1">
<Button Content="G" />
<Button Content="H" />
<Button Content="I" />
<Button Content="J" />
<Button Content="K" />
<Button Content="L" />
<Button Content="M" />
</UniformGrid>
<UniformGrid Grid.Row="2" Rows="1">
<Button Content="N" />
<Button Content="O" />
<Button Content="P" />
<Button Content="Q" />
<Button Content="R" />
<Button Content="S" />
<Button Content="T" />
</UniformGrid>
<UniformGrid Grid.Row="3" Rows="1">
<Button Content="U" />
<Button Content="V" />
<Button Content="W" />
<Button Content="X" />
<Button Content="Y" />
<Button Content="Z" />
</UniformGrid>
<TextBox Name="InputMessage" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" IsEnabled="False" Background="Beige" Grid.Row="4" Text="{Binding PasswordDisplay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</UserControl>
Here is the code-behind:
public partial class NodeGrid : UserControl
{
public NodeGrid()
{
InitializeComponent();
InputCommand = new InputCharacterCommand(this);
DataContext = this;
}
public string PasswordDisplay
{
get { return (string)GetValue(PasswordDisplayProperty); }
set { SetValue(PasswordDisplayProperty, value); }
}
public static readonly DependencyProperty PasswordDisplayProperty =
DependencyProperty.Register("PasswordDisplay", typeof(string), typeof(NodeGrid), new PropertyMetadata(""));
private ICommand _inputCommand;
public ICommand InputCommand
{
get
{
return _inputCommand;
}
set
{
_inputCommand = value;
}
}
public void AddCharacter(string input)
{
if (input != null)
{
PasswordDisplay = string.Concat(PasswordDisplay, input);
}
}
public bool InputAllowed()
{
if (PasswordDisplay == null)
{
return true;
}
if (PasswordDisplay.Length < 50)
{
return true;
}
return false;
}
private void OnPropertyChange([CallerMemberName] string property = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public event PropertyChangedEventHandler PropertyChanged;
}
class InputCharacterCommand : ICommand
{
private NodeGrid _vmodel;
public InputCharacterCommand(NodeGrid vm)
{
_vmodel = vm;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return (_vmodel.InputAllowed());
}
public void Execute(object parameter)
{
_vmodel.AddCharacter(parameter as string);
}
}
And here is how I use it in my MainWindow:
<StackPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<WrapPanel HorizontalAlignment="Center">
<Label Content="Enter PIN"/>
<TextBox CommandManager.PreviewCanExecute="HandleCanExecute" Foreground="Transparent" MinWidth="100" Padding="10,0" Text="{Binding PIN, UpdateSourceTrigger=PropertyChanged}"/>
</WrapPanel>
<customlock:NodeGrid MinHeight="250" MinWidth="500" PasswordDisplay="{Binding NodeGridDisplay}"/>
<Button VerticalAlignment="Center" HorizontalAlignment="Center" Content="Unlock!"/>
</StackPanel>
The Code-behind:
private void HandleCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (e.Command == ApplicationCommands.Cut ||
e.Command == ApplicationCommands.Copy ||
e.Command == ApplicationCommands.Paste)
{
e.CanExecute = false;
e.Handled = true;
}
}
And now, the ViewModel:
private string _PIN;
public string PIN
{
get
{
return _PIN;
}
set
{
if(value != _PIN)
{
_PIN = value;
OnPropertyChanged();
NodeGridDisplay = HashIt(_PIN);
}
}
}
private string _nodeGridDisplay;
public string NodeGridDisplay
{
get
{
return _nodeGridDisplay;
}
set
{
if (value != _nodeGridDisplay)
{
_nodeGridDisplay = value;
OnPropertyChanged();
}
}
}
private string HashIt(string input)
{
using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
{
return System.Text.Encoding.Default.GetString(md5.ComputeHash(System.Text.Encoding.ASCII.GetBytes(input))).GetHashCode().ToString();
}
}
What is the intended function?
Let me illustrate it.
The Lock
One can enter the PIN, the hash of which will be displayed in the NodeGrid. Then the user will click the letters which will be concatenated with the hash. Then the user can click unlock.
You should use ElementName in your UserControl or use RaltiveResource to find it, and you should not use DataContext = this. (see this answer and this link)
Give your userControl a name and write:
<UserControl x:Class= ......
Name="userControl">
<TextBox Text="{Binding Text, ElementName = userControl}"/>
</UserControl>
Please note that you should use Text instead of TextProperty and make sure to change _abc = "20" to _abc = value.
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
I have template for my ComboBox. taken from here. Lets say each item is Node type.
Its all work perfect, but one thing crash my mind. When I click between ComboBoxItem (this area i think one-pixel line), Text of ComboBox changed to TypeName of items (with namespace).
I have overrided ToString() method of Node class, but CallStack shows, that program comes to it from [External code].
What should I do to ComboBox.Text show my string property instead of TypeName of ComboBoxItem?
If I miss some detail, just point out what is.
Edit:
Code is from hyperlink here above, with some changes.
Usage:
<vm:ComboCheckBox Grid.Column="1" ItemsSource="{Binding Path=Months}"
DefaultText="Select months"/>
Months is ObservableNodeCollection.
Template[I don't place here resources. they are the same, as at link]:
<ComboBox ItemsSource="{Binding ElementName=UserControl, Path=ItemsSource}"
DataContext="{Binding ElementName=UserControl, Path=DataContext}"
Text="{Binding Path=Text, Mode=OneWay, ElementName=UserControl, UpdateSourceTrigger=PropertyChanged}"
Focusable="False"
IsEditable="False"
Loaded="CheckableCombo_Loaded"
x:Name="CheckableCombo"
SnapsToDevicePixels="True"
OverridesDefaultStyle="True"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="True"
IsSynchronizedWithCurrentItem="True"
MinWidth="120"
MinHeight="20"
>
<ComboBox.ItemTemplate>
<HierarchicalDataTemplate>
<CheckBox Margin="0" IsChecked="{Binding Path=IsSelected}"
Command="{Binding Path=CheckBoxStateChanged, ElementName=UserControl}" CommandParameter="{Binding}"
Content="{Binding Path=Title}" />
</HierarchicalDataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Template>
<ControlTemplate TargetType="ComboBox">
<Grid>
<ToggleButton
Name="ToggleButton"
Template="{StaticResource ComboBoxToggleButton}"
Grid.Column="2"
Focusable="false"
IsChecked="{Binding Path=IsDropDownOpen,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press">
</ToggleButton>
<ContentPresenter
x:Name="Presenter"
IsHitTestVisible="False"
Margin="3,3,23,3"
VerticalAlignment="Center"
HorizontalAlignment="Left">
<ContentPresenter.Content>
<TextBlock TextTrimming="CharacterEllipsis"
Text="{Binding Path=Text,Mode=OneWay,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ComboBox}}" />
</ContentPresenter.Content>
</ContentPresenter>
<TextBox x:Name="EditableTextBox"
Style="{x:Null}"
Template="{StaticResource ComboBoxTextBox}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="3,3,23,3"
Focusable="True"
Background="Transparent"
Visibility="Hidden"
IsReadOnly="{TemplateBinding IsReadOnly}"/>
<Popup Name="Popup"
Placement="Bottom"
IsOpen="{TemplateBinding IsDropDownOpen}"
AllowsTransparency="True"
Focusable="False"
PopupAnimation="Slide">
<Grid Name="DropDown"
ShowGridLines="True"
SnapsToDevicePixels="True"
MinWidth="{TemplateBinding ActualWidth}"
MaxHeight="{TemplateBinding MaxDropDownHeight}">
<Border Focusable="False"
x:Name="DropDownBorder"
Background="{StaticResource WindowBackgroundBrush}"
BorderThickness="1"
BorderBrush="{StaticResource SolidBorderBrush}"/>
<ScrollViewer Margin="4,6,4,6" SnapsToDevicePixels="True" DataContext="{Binding}">
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle" KeyboardNavigation.ControlTabNavigation="Cycle" />
</ScrollViewer>
</Grid>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasItems" Value="false">
<Setter TargetName="DropDownBorder" Property="MinHeight" Value="95"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{StaticResource DisabledForegroundBrush}"/>
</Trigger>
<Trigger Property="IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</Trigger>
<Trigger SourceName="Popup" Property="Popup.AllowsTransparency" Value="true">
<Setter TargetName="DropDownBorder" Property="CornerRadius" Value="4"/>
<Setter TargetName="DropDownBorder" Property="Margin" Value="0,2,0,0"/>
</Trigger>
<Trigger Property="IsEditable" Value="true">
<Setter Property="IsTabStop" Value="false"/>
<Setter TargetName="EditableTextBox" Property="Visibility" Value="Visible"/>
<Setter TargetName="Presenter" Property="Visibility" Value="Hidden"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ComboBox.Template>
</ComboBox>
CodeBehind:
/// <summary>
/// Interaction logic for ComboCheckBox.xaml
/// </summary>
public partial class ComboCheckBox : UserControl
{
public ObservableNodeCollection ItemsSource
{
get
{
return (ObservableNodeCollection)GetValue(ItemsSourceProperty);
}
set
{
SetValue(ItemsSourceProperty, value);
SetText();
}
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(ObservableNodeCollection), typeof(ComboCheckBox), new UIPropertyMetadata(null));
/// <summary>
/// Gets or sets the text displayed in the ComboBox
/// </summary>
public string Text
{
get
{
return(string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(ComboCheckBox), new UIPropertyMetadata(string.Empty));
/// <summary>
/// Gets or sets the text displayed in the ComboBox if there are no selected items
/// </summary>
public string DefaultText
{
get
{
return(string)GetValue(DefaultTextProperty);
}
set
{
SetValue(DefaultTextProperty, value);
SetText();
}
}
public static readonly DependencyProperty DefaultTextProperty =
DependencyProperty.Register("DefaultText", typeof(string), typeof(ComboCheckBox), new UIPropertyMetadata(string.Empty));
public ComboCheckBox()
{
InitializeComponent();
this.SetText();
}
public ICommand CheckBoxStateChanged
{
get
{
return new Helpers.DelegateCommand<Node>((Node parameter) =>
{
if (parameter.IsSelectAllNode)
{
bool? isSelected = this.ItemsSource[0].IsSelected;
ItemsSource.ToList().ForEach(item => item.IsSelected = isSelected);
}
else
{
if (this.ItemsSource[0].IsSelectAllNode)
{
bool isAllTrue = true, isAllFalse = true;
for (int i = 1; i < this.ItemsSource.Count; i++)
{
isAllTrue = isAllTrue && this.ItemsSource[i].IsSelected.Value;
isAllFalse = isAllFalse && !this.ItemsSource[i].IsSelected.Value;
}
if (isAllTrue)
this.ItemsSource[0].IsSelected = true;
else if (isAllFalse)
this.ItemsSource[0].IsSelected = false;
else
this.ItemsSource[0].IsSelected = null;
}
}
this.SetText();
});
}
}
private void SetText()
{
string answer = String.Empty;
if (ItemsSource != null)
answer = ItemsSource.ToString();
if (String.IsNullOrWhiteSpace(answer))
answer = this.DefaultText;
this.Text = answer;
}
private void CheckableCombo_Loaded(object sender, RoutedEventArgs e)
{
this.SetText();
}
}
Node class:
public class Node : ObservableObject
{
public Node(string title, string header = "", bool isSelectAllNode = false, bool? isSelected = false)
{
this.Title = title;
if (String.IsNullOrEmpty(header))
this.Header = title;
else
this.Header = header;
this.IsSelectAllNode = isSelectAllNode;
this.IsSelected = isSelected;
}
public string Header { get; set; }
public string Title { get; set; }
private bool? isSelected;
private const string IsSelectedPropertyName = "IsSelected";
public bool? IsSelected
{
get
{
return this.isSelected;
}
set
{
this.isSelected = value;
this.OnPropertyChanged(IsSelectedPropertyName);
}
}
public bool IsSelectAllNode { get; set; }
public override string ToString()
{
return "123";//if miss it, the typeName will return.
}
}
ObservableNodeCollection:
public class ObservableNodeCollection : ObservableCollection<Node>
{
public ObservableNodeCollection()
{
}
public ObservableNodeCollection(List<Node> list)
: base(list)
{
}
public ObservableNodeCollection(IEnumerable<Node> collection)
: base(collection)
{
}
public override string ToString()
{
string answer = String.Empty;
if (this.Items != null)
{
StringBuilder outString = new StringBuilder();
foreach (Node node in this.Items)
if (node.IsSelected == true && !node.IsSelectAllNode)
{
outString.Append(node.Header);
outString.Append(", ");
}
answer = outString.ToString().TrimEnd(new char[] { ',', ' ' });
}
return answer;
}
}
EDIT 2:
Inside of the ViewModel constructor of the main window exist this code
int i;
//Initialize months for combobox
this.Months = new ObservableNodeCollection(new List<Node>() { new Node("SelectAll", isSelectAllNode:true)});
for (i=0; i < DateTimeFormatInfo.CurrentInfo.MonthNames.Length; i++)
{
if (!String.IsNullOrEmpty(DateTimeFormatInfo.CurrentInfo.MonthNames[i]))
this.Months.Add(
new Node
(
DateTimeFormatInfo.CurrentInfo.MonthNames[i],
DateTimeFormatInfo.CurrentInfo.AbbreviatedMonthNames[i]
));
}
so, this.Months - is a ObservableNodeCollection, which have 1-st element is SelectAll, and other 12 elements is a months.
So you're saying that if you remove the Node.ToString() method it displays the TypeName of the Node?
That is the expected behavior. Your ComboBox items are bound to each item in your ObservableCollection, and when binding an object to a Text property, the ToString method is always used.
You can set the ComboBox.Text property manually, however it is changed to SelectedItem.ToString() whenever the selected ComboBox item changes. By default this resolves to the TypeName of the ComboBox's data item, however you can change this by overriding the ToString() method of the data item, or by setting the ComboBox.DisplayMemberPath to a property that exists on the data item
<ComboBox ItemsSource="{Binding Months}"
DisplayMemberPath="Header" />
Try the DisplayMemberPath property of your ComboBox
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.