Making a NxN tic tac toe GUI wpf c# - c#

I am making a NxN tic tac toe game in WPF c#. I want user to enter Rows and Column count(NxN), and my code will generate those number of columns and rows. I can't figure it out, how will I produce that number of rows and columns dynamically. I have posted my XAML code, is there a way that I can loop my XAML code?
thanks
<Grid x:Name="Container">
<!-- First here i want to make N columns-->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Here i want to make N rows-->
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Then here i want to add that number of buttons in N x N columns and rows -->
<Button x:Name="Button0_0" Grid.Row="0" Grid.Column="0" Click="Button_Click"/>
<Button x:Name="Button0_1" Grid.Row="0" Grid.Column="1" Click="Button_Click" />
<Button x:Name="Button0_2" Grid.Row="0" Grid.Column="2" Click="Button_Click"/>
<Button x:Name="Button1_0" Grid.Row="1" Grid.Column="0" Click="Button_Click"/>
<Button x:Name="Button1_1" Grid.Row="1" Grid.Column="1" Click="Button_Click"/>
<Button x:Name="Button1_2" Grid.Row="1" Grid.Column="2" Click="Button_Click"/>
<Button x:Name="Button2_0" Grid.Row="2" Grid.Column="0" Click="Button_Click"/>
<Button x:Name="Button2_1" Grid.Row="2" Grid.Column="1" Click="Button_Click"/>
<Button x:Name="Button2_2" Grid.Row="2" Grid.Column="2" Click="Button_Click"/>
</Grid>

ItemsControl + UniformGrid as a Panel is a good choice to display a rectangular game board. I have already posted similar solution (How to create and use matrix of (color) boxes C# WPF) but it is missing Rows and Columns binding. Here is a more elaborated example for TicTacToe.
Let's create view model classes:
public class BoardCell : INotifyPropertyChanged
{
private string _sign;
private bool _canSelect = true;
public string Sign
{
get { return _sign; }
set
{
_sign = value;
if (value != null)
CanSelect = false;
OnPropertyChanged();
}
}
public bool CanSelect
{
get { return _canSelect; }
set
{
_canSelect = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Board
{
public int Rows { get; set; }
public int Columns { get; set; }
private ObservableCollection<BoardCell> _cells;
public ObservableCollection<BoardCell> Cells
{
get
{
if (_cells == null)
_cells = new ObservableCollection<BoardCell>(Enumerable.Range(0, Rows*Columns).Select(i => new BoardCell()));
return _cells;
}
}
}
and then create a view:
<Grid x:Name="Container">
<Grid.DataContext>
<wpfDemos:Board Rows="8" Columns="8"/>
</Grid.DataContext>
<ItemsControl x:Name="Board" ItemsSource="{Binding Path=Cells}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate >
<UniformGrid Rows="{Binding Path=Rows}" Columns="{Binding Path=Columns}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Path=Sign}"
IsEnabled="{Binding Path=CanSelect}"
Click="CellClick"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
in the code-behind we have one method to allow 2 players make thier moves in turn:
private bool _firstPlayer = true;
private void CellClick(object sender, RoutedEventArgs e)
{
var cell = (sender as Button).DataContext as BoardCell;
cell.Sign = _firstPlayer ? "X" : "O";
_firstPlayer = !_firstPlayer;
// TODO check winner
}
to follow MVVM pattern this method should be wrapped into a command (ICommand), moved to Board class and attached to view via Button.Command binding.
summary: separate logic and presentation. work with data. let controls generate their content based on bindings and provided templates.

I have code for some AttachedProperties on my blog that does exactly this.
XAML code ends up looking like this :
<Grid local:GridHelpers.RowCount="{Binding RowCount}"
local:GridHelpers.ColumnCount="{Binding ColumnCount}" />
Here's the code for the Attached properties, in case the blog post link ever goes down. It also includes some properties to specify your Star rows/columns.
public class GridHelpers
{
#region RowCount Property
/// <summary>
/// Adds the specified number of Rows to RowDefinitions.
/// Default Height is Auto
/// </summary>
public static readonly DependencyProperty RowCountProperty =
DependencyProperty.RegisterAttached(
"RowCount", typeof(int), typeof(GridHelpers),
new PropertyMetadata(-1, RowCountChanged));
// Get
public static int GetRowCount(DependencyObject obj)
{
return (int)obj.GetValue(RowCountProperty);
}
// Set
public static void SetRowCount(DependencyObject obj, int value)
{
obj.SetValue(RowCountProperty, value);
}
// Change Event - Adds the Rows
public static void RowCountChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is Grid) || (int)e.NewValue < 0)
return;
Grid grid = (Grid)obj;
grid.RowDefinitions.Clear();
for (int i = 0; i < (int)e.NewValue; i++)
grid.RowDefinitions.Add(
new RowDefinition() { Height = GridLength.Auto });
SetStarRows(grid);
}
#endregion
#region ColumnCount Property
/// <summary>
/// Adds the specified number of Columns to ColumnDefinitions.
/// Default Width is Auto
/// </summary>
public static readonly DependencyProperty ColumnCountProperty =
DependencyProperty.RegisterAttached(
"ColumnCount", typeof(int), typeof(GridHelpers),
new PropertyMetadata(-1, ColumnCountChanged));
// Get
public static int GetColumnCount(DependencyObject obj)
{
return (int)obj.GetValue(ColumnCountProperty);
}
// Set
public static void SetColumnCount(DependencyObject obj, int value)
{
obj.SetValue(ColumnCountProperty, value);
}
// Change Event - Add the Columns
public static void ColumnCountChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is Grid) || (int)e.NewValue < 0)
return;
Grid grid = (Grid)obj;
grid.ColumnDefinitions.Clear();
for (int i = 0; i < (int)e.NewValue; i++)
grid.ColumnDefinitions.Add(
new ColumnDefinition() { Width = GridLength.Auto });
SetStarColumns(grid);
}
#endregion
#region StarRows Property
/// <summary>
/// Makes the specified Row's Height equal to Star.
/// Can set on multiple Rows
/// </summary>
public static readonly DependencyProperty StarRowsProperty =
DependencyProperty.RegisterAttached(
"StarRows", typeof(string), typeof(GridHelpers),
new PropertyMetadata(string.Empty, StarRowsChanged));
// Get
public static string GetStarRows(DependencyObject obj)
{
return (string)obj.GetValue(StarRowsProperty);
}
// Set
public static void SetStarRows(DependencyObject obj, string value)
{
obj.SetValue(StarRowsProperty, value);
}
// Change Event - Makes specified Row's Height equal to Star
public static void StarRowsChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is Grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
return;
SetStarRows((Grid)obj);
}
#endregion
#region StarColumns Property
/// <summary>
/// Makes the specified Column's Width equal to Star.
/// Can set on multiple Columns
/// </summary>
public static readonly DependencyProperty StarColumnsProperty =
DependencyProperty.RegisterAttached(
"StarColumns", typeof(string), typeof(GridHelpers),
new PropertyMetadata(string.Empty, StarColumnsChanged));
// Get
public static string GetStarColumns(DependencyObject obj)
{
return (string)obj.GetValue(StarColumnsProperty);
}
// Set
public static void SetStarColumns(DependencyObject obj, string value)
{
obj.SetValue(StarColumnsProperty, value);
}
// Change Event - Makes specified Column's Width equal to Star
public static void StarColumnsChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is Grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
return;
SetStarColumns((Grid)obj);
}
#endregion
private static void SetStarColumns(Grid grid)
{
string[] starColumns =
GetStarColumns(grid).Split(',');
for (int i = 0; i < grid.ColumnDefinitions.Count; i++)
{
if (starColumns.Contains(i.ToString()))
grid.ColumnDefinitions[i].Width =
new GridLength(1, GridUnitType.Star);
}
}
private static void SetStarRows(Grid grid)
{
string[] starRows =
GetStarRows(grid).Split(',');
for (int i = 0; i < grid.RowDefinitions.Count; i++)
{
if (starRows.Contains(i.ToString()))
grid.RowDefinitions[i].Height =
new GridLength(1, GridUnitType.Star);
}
}
}

As Zohar has mentioned you could use the code behind to add child GridColumn's to the Grid.ColumnDefinitions, and then add Button's to the Container Grid. Its an easy approach, but it means using code behind which causes many frowns.
Personally I would rather create a Behaviour and attach it to the Grid. Have your behaviour bind to an integer of Rows and Columns on your viewmodel, and then add ColumnDefinitions and RowDefinitions to the AssociatedObject. Behaviours are a great way to encapsulate reusable features, and eliminates the need for writing code behind.
This chap demonstrates how to create behaviours that bind to your model, and also how to attach them to an element in the view.
http://julmar.com/blog/programming/playing-with-wpf-behaviors-a-watermarktext-behavior/

Related

IValueConverter not "converting" upon item being added to list [duplicate]

I’m learning C# and building a UI that reads and writes integers to an XML config file. The UI uses a variety of custom user controls. I have a 3 radiobutton user control that binds to a single int variable (control returns 0,1,2). The control uses an event to trigger the update. It looks at the 3 isChecked properties to determine the new int value. But I don’t know how to update the original binding value from the code behind. It's once removed so to speak because there are two binds..one in the main window and one in the user control. As a beginner am lost at this point. BTW reading the int value into the 3 radiobuttons is working using a converter.
here is the user control xaml.cs...
namespace btsRV7config.controls
{
public partial class ui3X : UserControl
{
public ui3X()
{
InitializeComponent();
}
void _event(object sender, RoutedEventArgs e)
{
int newValue = 0;
if (rdbttn1.IsChecked == true) { newValue = 0; }
else if (rdbttn2.IsChecked == true) { newValue = 1; }
else if (rdbttn3.IsChecked == true) { newValue = 2; }
txtb.Text = newValue.ToString(); //tempRemove
// !!! assign newValue to Binding Source !!!
//---------------------------------------------
uiBinding1 = newValue;
BindingOperations.GetBindingExpression(rdbttn1, RadioButton.IsCheckedProperty).UpdateSource();
//---------------------------------------------
}
public static readonly DependencyProperty uiBinding1Property = DependencyProperty.Register("uiBinding1", typeof(int), typeof(ui3X));
public int uiBinding1
{
get { return (int)GetValue(uiBinding1Property); }
set { SetValue(uiBinding1Property, value); }
}
public static readonly DependencyProperty uiBinding2Property = DependencyProperty.Register("uiBinding2", typeof(int), typeof(ui3X));
public int uiBinding2
{
get { return (int)GetValue(uiBinding2Property); }
set { SetValue(uiBinding2Property, value); }
}
public static readonly DependencyProperty uiBinding3Property = DependencyProperty.Register("uiBinding3", typeof(int), typeof(ui3X));
public int uiBinding3
{
get { return (int)GetValue(uiBinding3Property); }
set { SetValue(uiBinding3Property, value); }
}
}
}
here is user control xaml
<UserControl x:Class="btsRV7config.controls.ui3X"
x:Name="root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Horizontal" Height="22">
<RadioButton Name="rdbttn1" VerticalAlignment="Center" Margin="0 0 10 0"
IsChecked="{Binding ElementName=root, Path=uiBinding1}"
Click="_event" />
<RadioButton Name="rdbttn2" VerticalAlignment="Center" Margin="0 0 10 0"
IsChecked="{Binding ElementName=root, Path=uiBinding2}"
Click="_event" />
<RadioButton Name="rdbttn3" VerticalAlignment="Center"
IsChecked="{Binding ElementName=root, Path=uiBinding3}"
Click="_event" />
<TextBox Name="txtb" Margin="5 0 0 0" Width="20" Height="17" /> <!-- tempRemove -->
</StackPanel>
</UserControl>
here is an example of the user control used in MainWindow.xaml
<Window x:Class="btsRV7config.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:btsRV7config.controls"
xmlns:converter="clr-namespace:btsRV7config.converters"
Title="Vans RV7 Configuration" Height="350" Width="525" >
<Window.Resources>
<converter:Int_Int_Bool_Converter x:Key="Int_Int_Bool" />
</Window.Resources>
<Grid>
<controls:ui3X uiName="Font Color" ui1="Black" ui2="Green" ui3="Cyan"
uiBinding1="{Binding RV7sld_DFfontColor, Converter={StaticResource Int_Int_Bool}, ConverterParameter=0}"
uiBinding2="{Binding RV7sld_DFfontColor, Converter={StaticResource Int_Int_Bool}, ConverterParameter=1}"
uiBinding3="{Binding RV7sld_DFfontColor, Converter={StaticResource Int_Int_Bool}, ConverterParameter=2}" />
</Grid>
</Window>
here is MainWindow.xaml.cs
namespace btsRV7config
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
record data = new record();
DataContext = data;
}
}
public class record : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _RV7sld_DFfontColor = RV7sld_dict["DFfontColor"];
public int RV7sld_DFfontColor
{
get
{ return _RV7sld_DFfontColor; }
set
{
_RV7sld_DFfontColor = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("RV7sld_DFfontColor"));
}
}
}
}
}
Sorry for posting so much code - I think the important is the user controls xaml.cs at top.
here is a link to a picture of the UI.
I've simplified the code I've posted to fit.
http://www.baytower.ca/photo/uiSample.jpg
So - 'Font Color'(RV7sld_DFfontColor) can be black(0) green(1) cyan(2)
Danny
The BindingOperations class enables you to "force" the binding updates in either direction.
Let's say there is a string property X in the code behind and there is a TextBox in XAML, bound to that property:
// C#:
public string X { get; set; }
// XAML:
<TextBox Name="textBox1" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:MainWindow, AncestorLevel=1}, Path=X}" />
To copy from textBox1.Text to X do the following:
BindingOperations.GetBindingExpression(textBox1, TextBox.TextProperty).UpdateSource();
To copy from X to textBox1.Text do the following:
BindingOperations.GetBindingExpression(textBox1, TextBox.TextProperty).UpdateTarget();

Add separate click events for multiple buttons in custom control

All:
The more I search for solutions to this question the more confused I become. After spending 12-16 hours watching YouTube, reading StackOverflow and general goggling, I thought I'd plead for additional help.
I would like to create a custom control so I can write various apps to remote to my video switcher.
I've created my control with dependency properties and that part is working well.
This answer on SO seemed to get me close, but I still can't get my app to run.
How to wire up a click event for a custom usercontrol button? Should I use CustomControl?
What I simply want to do is click btnIn1 in the control and have it return a "1", btnIn2 returns a "2" and so on.
I've also read about delegates, ICommand, TemplateParts and MVVM patterns which all seem like incredibly complex ways to click a button within a group. Maybe there's just not a simple way to do it.
Here's what I have so far. I simplified everything to a 2x2 matrix switcher (rather than the 4x4 I'm working on)
Thanks for all your help.
Norm
Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VideoSwitcher"
xmlns:enk="clr-namespace:VideoSwitcher.Controls">
<Style TargetType="{x:Type enk:Matrix44}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type enk:Matrix44}">
<Grid x:Name="grdBase" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40*"/>
<RowDefinition Height="100*"/>
<RowDefinition Height="40*"/>
<RowDefinition Height="100*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label x:Name="lblInputHeader" Content="Input"
Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="4"
Visibility="{TemplateBinding HeaderVisible}"
FontFamily="{TemplateBinding HeaderFont}"
FontSize="{TemplateBinding HeaderFontSize}"/>
<StackPanel Orientation="Horizontal"
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="4">
<Button x:Name="btnIn1"
Margin="{TemplateBinding ButtonMargin}"
Height="{TemplateBinding ButtonHeight}"
Width="{TemplateBinding ButtonWidth}"
Content="{TemplateBinding Input1Label}"
Click="btnIn1Click"/>
<Button x:Name="btnIn2"
Margin="{TemplateBinding ButtonMargin}"
Height="{TemplateBinding ButtonHeight}"
Width="{TemplateBinding ButtonWidth}"
Content="{TemplateBinding Input2Label}"
Click="btnIn2Click"/>
</StackPanel>
<Label x:Name="lblOutputHeader" Content="Output"
Grid.Column="0"
Grid.Row="2"
Grid.ColumnSpan="4"
Visibility="{TemplateBinding HeaderVisible}"
FontFamily="{TemplateBinding HeaderFont}"
FontSize="{TemplateBinding HeaderFontSize}"/>
<StackPanel Orientation="Horizontal"
Grid.Column="0"
Grid.Row="3"
Grid.ColumnSpan="4">
<Button x:Name="btnOut1"
Margin="{TemplateBinding ButtonMargin}"
Height="{TemplateBinding ButtonHeight}"
Width="{TemplateBinding ButtonWidth}"
Content="{TemplateBinding Output1Label}"
Click="btnOut1Click"/>
<Button x:Name="btnOut2"
Margin="{TemplateBinding ButtonMargin}"
Height="{TemplateBinding ButtonHeight}"
Width="{TemplateBinding ButtonWidth}"
Content="{TemplateBinding Output2Label}"
Click="btnOut2Click"/>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Custom Control (Matrix44.cs)
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.ComponentModel;
namespace VideoSwitcher.Controls
{
public class Matrix44 : Control
{
#region Events - Go here if I can ever find out how to use them
#endregion
//This does not work --------
public event RoutedEventHandler Click;
void btnIn1Click(object sender, RoutedEventArgs e)
{
if (this.Click != null)
{
this.Click(this, e);
}
}
void btnIn2Click(object sender, RoutedEventArgs e)
{
if (this.Click != null)
{
this.Click(this, e);
}
}
void btnOut1Click(object sender, RoutedEventArgs e)
{
if (this.Click != null)
{
this.Click(this, e);
}
}
void btnOut2Click(object sender, RoutedEventArgs e)
{
if (this.Click != null)
{
this.Click(this, e);
}
}
#region Properties - Exposed to the user in the Properties panel, XAML or code-behind
#region Switcher Appearance Properies (Height, Width, Margin)
[Category("Switcher Appearance Properties")]
public double ButtonHeight
{
get { return (double)GetValue(ButtonHeightProperty); }
set { SetValue(ButtonHeightProperty, value); }
}
public static readonly DependencyProperty ButtonHeightProperty =
DependencyProperty.Register(nameof(ButtonHeight), typeof(double), typeof(Matrix44), new PropertyMetadata(50.0));
[Category("Switcher Appearance Properties")]
public double ButtonWidth
{
get { return (double)GetValue(ButtonWidthProperty); }
set { SetValue(ButtonWidthProperty, value); }
}
public static readonly DependencyProperty ButtonWidthProperty =
DependencyProperty.Register(nameof(ButtonWidth), typeof(double), typeof(Matrix44), new PropertyMetadata(50.0));
[Category("Switcher Appearance Properties")]
public Thickness ButtonMargin
{
get { return (Thickness)GetValue(ButtonMarginProperty); }
set { SetValue(ButtonMarginProperty, value); }
}
public static readonly DependencyProperty ButtonMarginProperty =
DependencyProperty.Register("ButtonMargin", typeof(Thickness), typeof(Matrix44));
#endregion
#region Labels
[Category("Switcher Label Properties")]
public string Input1Label
{
get { return (string)GetValue(Input1LabelProperty); }
set { SetValue(Input1LabelProperty, value); }
}
public static readonly DependencyProperty Input1LabelProperty =
DependencyProperty.Register(nameof(Input1Label), typeof(string), typeof(Matrix44), new PropertyMetadata("Input 1"));
[Category("Switcher Label Properties")]
public string Input2Label
{
get { return (string)GetValue(Input2LabelProperty); }
set { SetValue(Input2LabelProperty, value); }
}
public static readonly DependencyProperty Input2LabelProperty =
DependencyProperty.Register(nameof(Input2Label), typeof(string), typeof(Matrix44), new PropertyMetadata("Input 2"));
[Category("Switcher Label Properties")]
public string Output1Label
{
get { return (string)GetValue(Output1LabelProperty); }
set { SetValue(Output1LabelProperty, value); }
}
public static readonly DependencyProperty Output1LabelProperty =
DependencyProperty.Register(nameof(Output1Label), typeof(string), typeof(Matrix44), new PropertyMetadata("Output 1"));
[Category("Switcher Label Properties")]
public string Output2Label
{
get { return (string)GetValue(Output2LabelProperty); }
set { SetValue(Output2LabelProperty, value); }
}
public static readonly DependencyProperty Output2LabelProperty =
DependencyProperty.Register(nameof(Output2Label), typeof(string), typeof(Matrix44), new PropertyMetadata("Output 2"));
#endregion
#region Header Properties
[Category("Switcher Header Properties")]
public Visibility HeaderVisible
{
get { return (Visibility)GetValue(HeaderVisibleProperty); }
set { SetValue(HeaderVisibleProperty, value); }
}
public static readonly DependencyProperty HeaderVisibleProperty =
DependencyProperty.Register("HeaderVisible", typeof(Visibility), typeof(Matrix44));
[Category("Switcher Header Properties")]
public FontFamily HeaderFont
{
get { return (FontFamily)GetValue(HeaderFontProperty); }
set { SetValue(HeaderFontProperty, value); }
}
public static readonly DependencyProperty HeaderFontProperty =
DependencyProperty.Register("HeaderFont", typeof(FontFamily), typeof(Matrix44));
[Category("Switcher Header Properties")]
public double HeaderFontSize
{
get { return (double)GetValue(HeaderFontSizeProperty); }
set { SetValue(HeaderFontSizeProperty, value); }
}
public static readonly DependencyProperty HeaderFontSizeProperty =
DependencyProperty.Register("HeaderFontSize", typeof(double), typeof(Matrix44));
#endregion
#region Channel Properties
[Category("Switcher Channel Properties")]
//Channel Property - use to extend switcher tool capabilties; e.g. add new bank of ins/outs and remap input 1 to input 5 on 2nd bank
public int Input1Channel
{
get { return (int)GetValue(Input1ChannelProperty); }
set { SetValue(Input1ChannelProperty, value); }
}
public static readonly DependencyProperty Input1ChannelProperty =
DependencyProperty.Register("Input1Channel", typeof(int), typeof(Matrix44), new PropertyMetadata(1));
[Category("Switcher Channel Properties")]
public bool Input1Enabled
{
get { return (bool)GetValue(Input1EnabledProperty); }
set { SetValue(Input1EnabledProperty, value); }
}
public static readonly DependencyProperty Input1EnabledProperty =
DependencyProperty.Register("Input1Enabled", typeof(bool), typeof(Matrix44), new PropertyMetadata(false));
[Category("Switcher Channel Properties")]
public int Input2Channel
{
get { return (int)GetValue(Input2ChannelProperty); }
set { SetValue(Input2ChannelProperty, value); }
}
public static readonly DependencyProperty Input2ChannelProperty =
DependencyProperty.Register("Input2Channel", typeof(int), typeof(Matrix44), new PropertyMetadata(2));
[Category("Switcher Channel Properties")]
public bool Input2Enabled
{
get { return (bool)GetValue(Input1EnabledProperty); }
set { SetValue(Input1EnabledProperty, value); }
}
[Category("Switcher Channel Properties")]
public int Output1Channel
{
get { return (int)GetValue(Output1ChannelProperty); }
set { SetValue(Output1ChannelProperty, value); }
}
//Output channels
public static readonly DependencyProperty Output1ChannelProperty =
DependencyProperty.Register("Output1Channel", typeof(int), typeof(Matrix44), new PropertyMetadata(1));
[Category("Switcher Channel Properties")]
public int Output2Channel
{
get { return (int)GetValue(Output2ChannelProperty); }
set { SetValue(Output2ChannelProperty, value); }
}
public static readonly DependencyProperty Output2ChannelProperty =
DependencyProperty.Register("Output2Channel", typeof(int), typeof(Matrix44), new PropertyMetadata(2));
#endregion
#endregion
public Matrix44()
{
DefaultStyleKey = typeof(Matrix44);
}
}
}
MainWindow.XAML
<Window
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:VideoSwitcher"
xmlns:Controls="clr-namespace:VideoSwitcher.Controls" x:Class="VideoSwitcher.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="200">
<Grid Margin="0,1,0,0">
<Controls:Matrix44 x:Name="swtMatrix"
HorizontalAlignment="Left"
Margin="10,10,0,0"
VerticalAlignment="Top"
ButtonHeight="65"
ButtonMargin="4"
ButtonWidth="65"/>
<Button x:Name="btnTake"
Content="Take"
HorizontalAlignment="Left"
Margin="10,213,0,0"
VerticalAlignment="Top"
Width="146"
Height="45" Click="btnTake_Click"/>
</Grid>
MainWindow.xaml.cs
using System.Windows;
namespace VideoSwitcher
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public int VideoInputChannel { get; set; }
public int VideoOutputChannel { get; set; }
public MainWindow()
{
InitializeComponent();
AddLabelsToMatrix();
}
public void AddLabelsToMatrix()
{
swtMatrix.Input1Label = "DVR1";
swtMatrix.Input2Label = "DVR2";
swtMatrix.Output1Label = "Videowall";
swtMatrix.Output2Label = "US Right";
}
private void btnTake_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Input channel: " + VideoInputChannel + " routed to Output: " + VideoOutputChannel);
}
/* switcher button psuedo-code
btnIn1_Click (object sender, RoutedEventArgs e)
{
//get input channel of matrix switcher
VideoInputChannel = swtMatrix.btnIn1.Channel;
}
btnIn2_Click (object sender, RoutedEventArgs e)
{
//get input channel of matrix switcher
VideoInputChannel = swtMatrix.btnIn2.Channel;
}
btnOut1_Click (object sender, RoutedEventArgs e)
{
//get output channel of matrix switcher
VideoInputChannel = swtMatrix.btnIn1.Channel;
}
btnOut2_Click (object sender, RoutedEventArgs e)
{
//get output channel of matrix switcher
VideoInputChannel = swtMatrix.btnIn2.Channel;
}
*/
}
}
You could override the OnApplyTemplate() method to get a reference to each Button and then hook up the event handlers:
public class Matrix44 : Control
{
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Button btnOut1 = this.Template.FindName("btnOut1", this) as Button;
if (btnOut1 != null)
btnOut1.Click += btnIn1Click;
//...and so on for each Button
}
}
You could then either raise a specific event for each Button or define a custom EventArgs that can be used to identify which Button that was clicked in an event handler:
C# event with custom arguments

Setting Columns and RowDefinitions in Resource Dictionary [duplicate]

I want to know how can I style a Grid so that I don't need to specify the
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" SharedSizeGroup="SG1"/>
<ColumnDefinition Width="auto" SharedSizeGroup="SG2"/>
</Grid.ColumnDefinitions>
every time?
Thank you very much!
ps: I did try to search on Google first. But I couldn't find any answer. Anyone who find the answer from google could you please tell me what keyword do you use to search? Sometimes I find it hard to determine what keyword use to search.
ps2: I am too lazy, every I just open chrome and type something and search. If nothing found, I conclude nothing found and come here. Is there anyone will search on Google, then find nothing, then open bing.com and search? And find nothing and go to yahoo and search and search?.....
It was always a pet peeve of mine to have to write out the RowDefinitions and ColumnDefinitions, so one day I got tired of it and wrote some attached properties that can be used for this kind of thing.
Now instead of writing my Grid definition like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
I can use
<Grid local:GridHelpers.RowCount="6"
local:GridHelpers.StarRows="5"
local:GridHelpers.ColumnCount="4"
local:GridHelpers.StarColumns="1,3">
</Grid>
It only allows for Auto and * sizes, but most of the time that's all I'm using.
It also supports bindings for dynamically sized Grids
<Grid local:GridHelpers.RowCount="{Binding RowCount}"
local:GridHelpers.ColumnCount="{Binding ColumnCount}" />
Here's a copy of the code in case that site ever goes down :
public class GridHelpers
{
#region RowCount Property
/// <summary>
/// Adds the specified number of Rows to RowDefinitions.
/// Default Height is Auto
/// </summary>
public static readonly DependencyProperty RowCountProperty =
DependencyProperty.RegisterAttached(
"RowCount", typeof(int), typeof(GridHelpers),
new PropertyMetadata(-1, RowCountChanged));
// Get
public static int GetRowCount(DependencyObject obj)
{
return (int)obj.GetValue(RowCountProperty);
}
// Set
public static void SetRowCount(DependencyObject obj, int value)
{
obj.SetValue(RowCountProperty, value);
}
// Change Event - Adds the Rows
public static void RowCountChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is Grid) || (int)e.NewValue < 0)
return;
Grid grid = (Grid)obj;
grid.RowDefinitions.Clear();
for (int i = 0; i < (int)e.NewValue; i++)
grid.RowDefinitions.Add(
new RowDefinition() { Height = GridLength.Auto });
SetStarRows(grid);
}
#endregion
#region ColumnCount Property
/// <summary>
/// Adds the specified number of Columns to ColumnDefinitions.
/// Default Width is Auto
/// </summary>
public static readonly DependencyProperty ColumnCountProperty =
DependencyProperty.RegisterAttached(
"ColumnCount", typeof(int), typeof(GridHelpers),
new PropertyMetadata(-1, ColumnCountChanged));
// Get
public static int GetColumnCount(DependencyObject obj)
{
return (int)obj.GetValue(ColumnCountProperty);
}
// Set
public static void SetColumnCount(DependencyObject obj, int value)
{
obj.SetValue(ColumnCountProperty, value);
}
// Change Event - Add the Columns
public static void ColumnCountChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is Grid) || (int)e.NewValue < 0)
return;
Grid grid = (Grid)obj;
grid.ColumnDefinitions.Clear();
for (int i = 0; i < (int)e.NewValue; i++)
grid.ColumnDefinitions.Add(
new ColumnDefinition() { Width = GridLength.Auto });
SetStarColumns(grid);
}
#endregion
#region StarRows Property
/// <summary>
/// Makes the specified Row's Height equal to Star.
/// Can set on multiple Rows
/// </summary>
public static readonly DependencyProperty StarRowsProperty =
DependencyProperty.RegisterAttached(
"StarRows", typeof(string), typeof(GridHelpers),
new PropertyMetadata(string.Empty, StarRowsChanged));
// Get
public static string GetStarRows(DependencyObject obj)
{
return (string)obj.GetValue(StarRowsProperty);
}
// Set
public static void SetStarRows(DependencyObject obj, string value)
{
obj.SetValue(StarRowsProperty, value);
}
// Change Event - Makes specified Row's Height equal to Star
public static void StarRowsChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is Grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
return;
SetStarRows((Grid)obj);
}
#endregion
#region StarColumns Property
/// <summary>
/// Makes the specified Column's Width equal to Star.
/// Can set on multiple Columns
/// </summary>
public static readonly DependencyProperty StarColumnsProperty =
DependencyProperty.RegisterAttached(
"StarColumns", typeof(string), typeof(GridHelpers),
new PropertyMetadata(string.Empty, StarColumnsChanged));
// Get
public static string GetStarColumns(DependencyObject obj)
{
return (string)obj.GetValue(StarColumnsProperty);
}
// Set
public static void SetStarColumns(DependencyObject obj, string value)
{
obj.SetValue(StarColumnsProperty, value);
}
// Change Event - Makes specified Column's Width equal to Star
public static void StarColumnsChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is Grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
return;
SetStarColumns((Grid)obj);
}
#endregion
private static void SetStarColumns(Grid grid)
{
string[] starColumns =
GetStarColumns(grid).Split(',');
for (int i = 0; i < grid.ColumnDefinitions.Count; i++)
{
if (starColumns.Contains(i.ToString()))
grid.ColumnDefinitions[i].Width =
new GridLength(1, GridUnitType.Star);
}
}
private static void SetStarRows(Grid grid)
{
string[] starRows =
GetStarRows(grid).Split(',');
for (int i = 0; i < grid.RowDefinitions.Count; i++)
{
if (starRows.Contains(i.ToString()))
grid.RowDefinitions[i].Height =
new GridLength(1, GridUnitType.Star);
}
}
}
This is a solution which doesn't require any helper class.
It's possible to set ColumnDefinitions by using an ItemsControl with a Grid as its ItemsPanelTemplate. This is shown in the example below.
<ItemsControl>
<ItemsControl.Resources>
<Style TargetType="ItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Resources>
<TextBox Text="First column" />
<TextBox Text="second column" Grid.Column="1" />
</ItemsControl>
Create attached dependency property with change callback to synchronize collection elements:
<Grid>
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="my:GridUtils.ColumnDefinitions">
<Setter.Value>
<my:ColumnDefinitionCollection>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</my:ColumnDefinitionCollection>
</Setter.Value>
</Setter>
</Style>
</Grid.Style>
<Button Content="Button" />
<Button Content="Button" Grid.Column="1" />
</Grid>
Implementation (RowDefinition support omitted as it's basically identical):
public class GridUtils
{
public static readonly DependencyProperty ColumnDefinitionsProperty =
DependencyProperty.RegisterAttached("ColumnDefinitions", typeof (ColumnDefinitionCollection),
typeof (GridUtils),
new PropertyMetadata(default(ColumnDefinitionCollection),
OnColumnDefinitionsChanged));
private static void OnColumnDefinitionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs ev)
{
var grid = (Grid) d;
var oldValue = (ColumnDefinitionCollection) ev.OldValue;
var newValue = (ColumnDefinitionCollection) ev.NewValue;
grid.ColumnDefinitions.Clear();
if (newValue != null)
foreach (var cd in newValue)
grid.ColumnDefinitions.Add(cd);
}
public static void SetColumnDefinitions(Grid element, ColumnDefinitionCollection value)
{
element.SetValue(ColumnDefinitionsProperty, value);
}
public static ColumnDefinitionCollection GetColumnDefinitions(Grid element)
{
return (ColumnDefinitionCollection) element.GetValue(ColumnDefinitionsProperty);
}
}
public class ColumnDefinitionCollection : List<ColumnDefinition> {}
I believe it's not possible because you can't set a style that affects all ColumnDefinition(s).
Grid does not support ControlTemplate, so you can't do it with composition.
The only hack I can think of would be to create a user control with those 2 columns and extend the grid. But that's nasty.
Here is a way:
1) Create a collection with an attached property like this:
public class ColumnDefinitions : Collection<ColumnDefinition>
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached(
"Source",
typeof(ColumnDefinitions),
typeof(ColumnDefinitions),
new PropertyMetadata(
default(ColumnDefinitions),
OnColumnDefinitionsChanged));
public static void SetSource(Grid element, ColumnDefinitions value)
{
element.SetValue(SourceProperty, value);
}
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(Grid))]
public static ColumnDefinitions GetSource(Grid element)
{
return (ColumnDefinitions)element.GetValue(SourceProperty);
}
private static void OnColumnDefinitionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = (Grid)d;
grid.ColumnDefinitions.Clear();
var columnDefinitions = (ColumnDefinitions)e.NewValue;
if (columnDefinitions == null)
{
return;
}
foreach (var columnDefinition in columnDefinitions)
{
grid.ColumnDefinitions.Add(columnDefinition);
}
}
}
2) Then you can use it as a resource and in a style for grid like this:
Note that x:Shared="False" must be used. If not the same definition will be added to many grids causing WPF to throw.
<UserControl.Resources>
<demo:ColumnDefinitions x:Key="SomeColumnDefinitions" x:Shared="False">
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</demo:ColumnDefinitions>
<Style x:Key="SomeGridStyle" TargetType="{x:Type Grid}">
<Setter Property="demo:ColumnDefinitions.Source" Value="{StaticResource SomeColumnDefinitions}"></Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="5"/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid Style="{StaticResource SomeGridStyle}">
<Rectangle Grid.Row="0"
Grid.Column="0"
Width="120"
Fill="Blue" />
<Rectangle Grid.Row="0"
Grid.Column="1"
Fill="Yellow" />
</Grid>
<Grid Grid.Row="2" Style="{StaticResource SomeGridStyle}">
<Rectangle Grid.Row="0"
Grid.Column="0"
Width="120"
Fill="Blue" />
<Rectangle Grid.Row="0"
Grid.Column="1"
Fill="Yellow" />
</Grid>
</Grid>

ListBox does not stay within Window borders when put into a Grid with rows having MinHeight

I have a Grid with two rows sized in 1:3 proportion; the first row has MinHeight set to a non-zero value. When I put a ListBox into the second row, its size is not limited by the window borders:
The problem occurs if MinHeight is applied (that is, if the window is small). If I replace the problematic ListBox with a Button, the problem disappears (buttons always stay within window borders).
MainWindow.xaml
<Window x:Class="WpfGridLayoutMinMax.MainWindow" x:Name="self"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300" MinHeight="200" MinWidth="200">
<Control.Resources>
<Style TargetType="ListBox">
<Setter Property="Margin" Value="4"/>
<Setter Property="ItemsSource" Value="{Binding Items, ElementName=self}"/>
</Style>
</Control.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" MinHeight="100"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0"/>
<ListBox Grid.Row="1"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Collections.Generic;
using System.Linq;
namespace WpfGridLayoutMinMax
{
public partial class MainWindow
{
public List<int> Items { get; set; }
public MainWindow ()
{
Items = Enumerable.Range(0, 20).ToList();
InitializeComponent();
}
}
}
Question: What causes this problem? How to make ListBox stay within window content area?
This problem appears every time the content is too large (for Button too if you set height larger than remaining space).
Don't know if you like my solution but I've added an additional Grid for measuring the remaining space. Unfortunatly it is not possible to get ActualHeight from second RowDefinition directly (it has no usable value). That's why I've added one more control (the Dummy). Now you can limit ListBox.MaxHeight to Dummy.ActualHeight and it stays within window.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid x:Name="Dummy" Grid.Row="1" />
<Grid Grid.RowSpan="2">
<Grid.RowDefinitions>
<RowDefinition Height="1*" MinHeight="100"/>
<RowDefinition Height="3*" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0" />
<ListBox Grid.Row="1" MaxHeight="{Binding ActualHeight, ElementName=Dummy}" />
</Grid>
</Grid>
If you set two rows with 1* and 3*, and say that 1* is minimally 100(px) high, then the second row wil minimally be 300(px). Makes sense right? So if you want to keep this ratio, and want to keep the min height for the first row you could set the minheight of you window to 400(or a little bit more).
The problem is caused by the buggy Grid. Its MeasureOverride returns a bigger size than the constraint, even though there's no reason for this.
I've implemented ForceCellSizes attached property which fixes the issue.
public static class GridProps
{
public static readonly DependencyProperty CalculateCellSizesProperty = DependencyProperty.RegisterAttached(
"CalculateCellSizes", typeof(bool), typeof(GridProps),
new PropertyMetadata(false, (o, a) => CalculateCellSizes_OnChanged((Grid)o, a)));
public static readonly DependencyProperty ForceCellSizesProperty = DependencyProperty.RegisterAttached(
"ForceCellSizes", typeof(bool), typeof(GridProps),
new PropertyMetadata(false, (o, a) => ForceCellSizes_OnChanged((Grid)o, a)));
private static readonly DependencyProperty DummyGridProperty = DependencyProperty.RegisterAttached(
"DummyGrid", typeof(Grid), typeof(GridProps),
new PropertyMetadata(null));
private static readonly DependencyPropertyKey RowActualHeightPropertyKey = DependencyProperty.RegisterAttachedReadOnly(
"RowActualHeight", typeof(double), typeof(GridProps),
new PropertyMetadata(0.0));
public static readonly DependencyProperty RowActualHeightProperty = RowActualHeightPropertyKey.DependencyProperty;
private static readonly DependencyPropertyKey ColumnActualWidthPropertyKey = DependencyProperty.RegisterAttachedReadOnly(
"ColumnActualWidth", typeof(double), typeof(GridProps),
new PropertyMetadata(0.0));
public static readonly DependencyProperty ColumnActualWidthProperty = ColumnActualWidthPropertyKey.DependencyProperty;
public static bool GetCalculateCellSizes (Grid grid)
{
return (bool)grid.GetValue(CalculateCellSizesProperty);
}
public static void SetCalculateCellSizes (Grid grid, bool value)
{
grid.SetValue(CalculateCellSizesProperty, value);
}
public static bool GetForceCellSizes (Grid grid)
{
return (bool)grid.GetValue(ForceCellSizesProperty);
}
public static void SetForceCellSizes (Grid grid, bool value)
{
grid.SetValue(ForceCellSizesProperty, value);
}
private static Grid GetDummyGrid (Grid grid)
{
return (Grid)grid.GetValue(DummyGridProperty);
}
private static void SetDummyGrid (Grid grid, Grid value)
{
grid.SetValue(DummyGridProperty, value);
}
public static double GetRowActualHeight (RowDefinition row)
{
return (double)row.GetValue(RowActualHeightProperty);
}
private static void SetRowActualHeight (RowDefinition row, double value)
{
row.SetValue(RowActualHeightPropertyKey, value);
}
public static double GetColumnActualWidth (ColumnDefinition column)
{
return (double)column.GetValue(ColumnActualWidthProperty);
}
private static void SetColumnActualWidth (ColumnDefinition column, double value)
{
column.SetValue(ColumnActualWidthPropertyKey, value);
}
private static void CalculateCellSizes_OnChanged (Grid grid, DependencyPropertyChangedEventArgs args)
{
if ((bool)args.NewValue)
grid.SizeChanged += Grid_OnSizeChanged;
else
grid.SizeChanged -= Grid_OnSizeChanged;
}
private static void Grid_OnSizeChanged (object sender, SizeChangedEventArgs args)
{
var grid = (Grid)sender;
foreach (RowDefinition row in grid.RowDefinitions)
SetRowActualHeight(row, row.ActualHeight);
foreach (ColumnDefinition column in grid.ColumnDefinitions)
SetColumnActualWidth(column, column.ActualWidth);
}
private static void ForceCellSizes_OnChanged (Grid grid, DependencyPropertyChangedEventArgs args)
{
if ((bool)args.NewValue) {
Action initDummyGrid = () => {
Grid parentGrid = (Grid)grid.Parent, dummyGrid = CreateDummyGrid(grid);
parentGrid.Children.Add(dummyGrid);
SetDummyGrid(grid, dummyGrid);
};
if (grid.IsLoaded)
initDummyGrid();
else
grid.Loaded += (o, e) => initDummyGrid();
}
else {
Grid parentGrid = (Grid)grid.Parent, dummyGrid = DestroyDummyGrid(grid);
parentGrid.Children.Remove(dummyGrid);
SetDummyGrid(grid, null);
}
}
private static Grid CreateDummyGrid (Grid grid)
{
var dummyGrid = new Grid { Visibility = Visibility.Hidden };
SetCalculateCellSizes(dummyGrid, true);
foreach (RowDefinition row in grid.RowDefinitions) {
var dummyRow = new RowDefinition { Height = row.Height, MinHeight = row.MinHeight, MaxHeight = row.MaxHeight };
dummyGrid.RowDefinitions.Add(dummyRow);
BindingOperations.SetBinding(row, RowDefinition.HeightProperty,
new Binding { Source = dummyRow, Path = new PropertyPath(RowActualHeightProperty) });
}
foreach (ColumnDefinition column in grid.ColumnDefinitions) {
var dummyColumn = new ColumnDefinition { Width = column.Width, MinWidth = column.MinWidth, MaxWidth = column.MaxWidth };
dummyGrid.ColumnDefinitions.Add(dummyColumn);
BindingOperations.SetBinding(column, ColumnDefinition.WidthProperty,
new Binding { Source = dummyColumn, Path = new PropertyPath(ColumnActualWidthProperty) });
}
return dummyGrid;
}
private static Grid DestroyDummyGrid (Grid grid)
{
Grid dummyGrid = GetDummyGrid(grid);
SetCalculateCellSizes(dummyGrid, false);
foreach (RowDefinition row in grid.RowDefinitions)
BindingOperations.ClearBinding(row, RowDefinition.HeightProperty);
foreach (ColumnDefinition column in grid.ColumnDefinitions)
BindingOperations.ClearBinding(column, ColumnDefinition.WidthProperty);
return dummyGrid;
}
}
Attached properties
The following attached properties are defined in the GridProps class:
Grid.CalculateCellSizes (read/write) — adds bindable RowActualHeight and ColumnActualWidth properties to RowDefinitions and ColumnDefinitions of the grid, respectively.
Grid.ForceCellSizes (read/write) — fixes the problem described in the question.
RowDefinition.RowActualHeight (read-only) — bindable RowDefinition.ActualHeight property. Set CalculateCellSizes on the owner grid to true.
ColumnDefinition.ColumnActualWidth (read-only) — bindable ColumnDefinition.ActualWidth property. Set CalculateCellSizes on the owner grid to true.
How to use
Wrap problematic Grid in an empty Grid.
Set GridProps.ForceCellSizes to true. Example from the question becomes:
<Grid>
<Grid local:GridProps.ForceCellSizes="True">
<Grid.RowDefinitions>
<RowDefinition Height="1*" MinHeight="100"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0"/>
<ListBox Grid.Row="1"/>
</Grid>
</Grid>
How it works
It adds an empty dummy grid with the same rows and columns as in the original grid, then binds heights and widths of the original grid to actual heights and widths of the dummy grid.
Essentially, the example above becomes:
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding RowDefinitions[0].(local:GridProps.RowActualHeight), ElementName=dummyGrid}" MinHeight="100"/>
<RowDefinition Height="{Binding RowDefinitions[1].(local:GridProps.RowActualHeight), ElementName=dummyGrid}"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0"/>
<ListBox Grid.Row="1"/>
</Grid>
<Grid x:Name="dummyGrid" Visibility="Hidden" local:GridProps.CalculateCellSizes="True">
<Grid.RowDefinitions>
<RowDefinition Height="1*" MinHeight="100"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
</Grid>
</Grid>

How can I databind a list of integers to Grid ColumnDefinitions

I have a set of integers:
public ObservableCollection<int> Scores = new ObservableCollection<int> {
10, 30, 50
};
Which I would like to result in something that renders like the following XAML when bound:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0">10</TextBlock>
<TextBlock Grid.Column="1">30</TextBlock>
<TextBlock Grid.Column="2">50</TextBlock>
</Grid>
How can I write a databinding to do this?
You could try something like the following:
<ItemsControl ItemsSource="{Binding Path=Scores}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1"
Background="Yellow" Width="{Binding}">
<TextBlock Text="{Binding}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
I'm using a Border with a TextBlock inside it, but you can replace that with something else if you wish. The important thing is the Width binding.
Note also that Scores must be a property. In your code above you're creating a public field, but binding only works with properties, not with fields.
EDIT: if you want to use a Grid, you could try something like the following user control. This user control has a dependency property for the widths of the grid, and recreates the grid every time the collection changes.
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
namespace YourNamespace
{
public partial class BindableGrid : UserControl
{
public static readonly DependencyProperty WidthsProperty =
DependencyProperty.Register("Widths",
typeof(ObservableCollection<int>),
typeof(BindableGrid),
new PropertyMetadata(Widths_Changed));
public BindableGrid()
{
InitializeComponent();
}
public ObservableCollection<int> Widths
{
get { return (ObservableCollection<int>)GetValue(WidthsProperty); }
set { SetValue(WidthsProperty, value); }
}
private static void Widths_Changed(DependencyObject obj,
DependencyPropertyChangedEventArgs e)
{
var grid = obj as BindableGrid;
if (grid != null)
{
grid.OnWidthsChanged(e.OldValue as ObservableCollection<int>);
}
}
private void OnWidthsChanged(ObservableCollection<int> oldValue)
{
if (oldValue != null)
{
oldValue.CollectionChanged -= Widths_CollectionChanged;
}
if (Widths != null)
{
Widths.CollectionChanged += Widths_CollectionChanged;
}
RecreateGrid();
}
private void Widths_CollectionChanged(object sender,
NotifyCollectionChangedEventArgs e)
{
// We'll just clear and recreate the entire grid each time
// the collection changes.
// Alternatively, you could use e.Action to determine what the
// actual change was and apply that (e.g. add or delete a
// single column definition).
RecreateGrid();
}
private void RecreateGrid()
{
// Recreate the column definitions.
grid.ColumnDefinitions.Clear();
foreach (int width in Widths)
{
// Use new GridLength(1, GridUnitType.Star) for a "*" column.
var coldef = new ColumnDefinition() { Width = new GridLength(width) };
grid.ColumnDefinitions.Add(coldef);
}
// Now recreate the content of the grid.
grid.Children.Clear();
for (int i = 0; i < Widths.Count; ++i)
{
int width = Widths[i];
var textblock = new TextBlock() { Text = width.ToString() };
Grid.SetColumn(textblock, i);
grid.Children.Add(textblock);
}
}
}
}
The XAML for this UserControl contains only <Grid x:Name="grid" /> inside the <UserControl> element.
You could then use it in XAML as follows, assuming you've bound somePrefix to the namespace YourNamespace:
<somePrefix:BindableGrid Widths="{Binding Path=Scores}" />

Categories

Resources