Render a custom attached property value - wpf

I need to do this in XAML :
<Grid x:Name="layoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<SomeUserControl Grid.Row="0" />
<ui:AdditionalView.UseCase1>
<ContentControl>
<TextBlock>aaa</TextBlock>
</ContentControl>
</ui:AdditionalView.UseCase1>
</Grid>
First, and most important, I need to have the block in the Something.UseCase1 form. That is how I ended up using attached properties. I defined the AdditionalView class and defined an attached property called UseCase1 on it.
However this does not render the
<TextBlock>aaa</TextBlock>
at runtime.
How can I achieve this?
Later Edit (1) - I managed to get something to work like so :
<ContentControl Grid.Row="1" Content="{Binding ElementName=layoutRoot, Path=(ui:AdditionalView.UseCase1)}" />
.. but it seems nasty. Any decent way to get this to work?
The AdditionalView class :
public class AdditionalView
{
public static readonly DependencyProperty UseCase1Property = DependencyProperty.RegisterAttached(
"UseCase1",
typeof(object),
typeof(AdditionalView),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender)
);
public static void SetUseCase1(UIElement element, Boolean value)
{
element.SetValue(UseCase1Property, value);
}
public static object GetUseCase1(UIElement element)
{
return element.GetValue(UseCase1Property);
}
}

Related

Bind view models' property to dependency property

I have created reusable components let's say a label and a textbox:
HeaderAndTextBox.xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<TextBlock
Margin="10,0,0,0"
FontSize="16"
FontWeight="DemiBold"
Foreground="White"
Text="{Binding Header, ElementName=root}" />
<TextBox
Grid.Row="1"
MaxWidth="300"
Margin="10"
mah:TextBoxHelper.ClearTextButton="True"
mah:TextBoxHelper.IsWaitingForData="True"
FontSize="16"
Text="{Binding TextBoxContent, ElementName=root}" />
</Grid>
Now as you can see I created dependency properties for the Text properties. Here is the code behind:
HeaderAndTextBox.xaml.cs
public partial class HeaderAndTextBox : UserControl
{
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(string), typeof(HeaderAndTextBox), new PropertyMetadata(string.Empty));
public string Header
{
get { return (string)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public static readonly DependencyProperty TextBoxContentProperty =
DependencyProperty.Register("TextBoxContent", typeof(string), typeof(HeaderAndTextBox), new PropertyMetadata(string.Empty));
public string TextBoxContent
{
get { return (string)GetValue(TextBoxContentProperty); }
set { SetValue(TextBoxContentProperty, value); }
}
public HeaderAndTextBox()
{
InitializeComponent();
}
}
In my view I use this reusable component like this:
MyView.xaml
<controls:HeaderAndTextBox
Grid.Row="1"
Margin="10,10,0,0"
Header="Last Name"
TextBoxContent="{Binding Path=LastName, UpdateSourceTrigger=PropertyChanged}" />
And my view model:
MyViewModel.cs
private string? _lastName;
public string? LastName
{
get
{
return _lastName;
}
set
{
_lastName = value;
OnPropertyChanged(nameof(LastName));
}
}
Question is, how can I bind this dependency property to my view model's property? As my approach doesn't work. I have more than one property so I must find a solution for the binding to be dynamic.
Could it be that for this kind of problem, I should use a completely different approach?
The internal elements must bind to the control's properties either by Binding.ElementName, where the the named UserControl is the binding source or by using Binding.RelativeSource.
HeaderAndTextBox.xaml
<UserControl>
<TextBox Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=TextBoxContent, UpdateSourceTrigger=PropertyChanged}" />
</UserControl>
Next, make sure the DataContext of the parent element that hosts HeaderAndTextBox is correct:
MainWindow.xaml
<Window>
<Window.DataContext>
<MyViewModel />
</Window.DataContext>
<StackPanel>
<!-- The HeaderAndTextBox inherits the parent's DataContext,
which is MyViewModel, automatically. -->
<HeaderAndTextBox TextBoxContent="{Binding SomeMyViewModelTextProperty}" />
<Grid DataContext="{Binding GridViewModel}">
<!-- Same control, different instance,
binds to a different view model class (GridViewModel). -->
<HeaderAndTextBox TextBoxContent="{Binding SomeGridViewModelTextProperty}" />
</Grid>
</StackPanel>
</Window>
To make the HeaderAndTextBox.TextBoxContent property send data back to the view model automatically (when typing into the TextBox), you should configure the dependency property accordingly by using a FrameworkPropertyMetadata object instead of a PropertyMetadata and set the FrameworkPropertyMetadata.BindsTwoWayByDefault property:
HeaderAndTextBox.xaml.cs
partial class HeaderAndTextBox : UserControl
{
public static readonly DependencyProperty TextBoxContentProperty = DependencyProperty.Register(
"TextBoxContent",
typeof(string),
typeof(HeaderAndTextBox),
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string TextBoxContent
{
get => (string)GetValue(TextBoxContentProperty);
set => SetValue(TextBoxContentProperty, value);
}
}

Is there a XAML equivalent to CSS's grid-area?

Does WPF XAML have an equivalent to CSS's grid-area? I.e., a way to create a definition of Row, Column, RowSpan, ColumnSpan values, give that definition an identifier, and then use those values via the identifier?
I am imagining something like:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.AreaDefinitions>
<AreaDefinition Row="0" Column="0" ColumnSpan="2" Name="Header" />
<AreaDefinition Row="1" Column="0" Name="Navigation" />
<AreaDefinition Row="1" Column="1" Name="Main" />
<AreaDefinition Row="2" Column="0" ColumnSpan="2" Name="Footer" />
</Grid.AreaDefinitions>
<TextBlock Grid.Area="Header" Text="Header" />
<TextBlock Grid.Area="Navigation" Text="Navigation" />
<TextBlock Grid.Area="Main" Text="Main" />
<TextBlock Grid.Area="Footer" Text="Footer" />
</grid>
WPF doesn't support this directly, but it's easy enough to implement with dependency properties and attached properties. First you'll need a class for your AreaDefinition:
public class AreaDefinition : DependencyObject
{
public int Row
{
get { return (int)GetValue(RowProperty); }
set { SetValue(RowProperty, value); }
}
public static readonly DependencyProperty RowProperty =
DependencyProperty.Register("Row", typeof(int), typeof(AreaDefinition), new PropertyMetadata(0));
public int Column
{
get { return (int)GetValue(ColumnProperty); }
set { SetValue(ColumnProperty, value); }
}
public static readonly DependencyProperty ColumnProperty =
DependencyProperty.Register("Column", typeof(int), typeof(AreaDefinition), new PropertyMetadata(0));
public int RowSpan
{
get { return (int)GetValue(RowSpanProperty); }
set { SetValue(RowSpanProperty, value); }
}
public static readonly DependencyProperty RowSpanProperty =
DependencyProperty.Register("RowSpan", typeof(int), typeof(AreaDefinition), new PropertyMetadata(1));
public int ColumnSpan
{
get { return (int)GetValue(ColumnSpanProperty); }
set { SetValue(ColumnSpanProperty, value); }
}
public static readonly DependencyProperty ColumnSpanProperty =
DependencyProperty.Register("ColumnSpan", typeof(int), typeof(AreaDefinition), new PropertyMetadata(1));
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(AreaDefinition), new PropertyMetadata(String.Empty));
}
You'll also need a class to hold a collection of these, in much the same way that Grid.Columns is a collection of type ColumnDefinitionCollection:
public class AreaDefinitionCollection : Collection<AreaDefinition>
{
}
Last, you'll need a class for your attached properties, which I'll call GridHelper. This class will need to provide two APs, one for your Grids (GridHelper.AreaDefinitions) and another for the Grid's children (GridHelper.Area). The change handler for GridHelper.AreaProperty is where all the magic happens, it simply updates the associated Grid APs whenever the Area changes:
public static class GridHelper
{
public static AreaDefinitionCollection GetAreaDefinitions(DependencyObject obj)
{
return (AreaDefinitionCollection)obj.GetValue(AreaDefinitionsProperty);
}
public static void SetAreaDefinitions(DependencyObject obj, AreaDefinitionCollection value)
{
obj.SetValue(AreaDefinitionsProperty, value);
}
public static readonly DependencyProperty AreaDefinitionsProperty =
DependencyProperty.RegisterAttached("AreaDefinitions", typeof(AreaDefinitionCollection), typeof(Grid), new PropertyMetadata(new AreaDefinitionCollection()));
public static string GetArea(DependencyObject obj)
{
return (string)obj.GetValue(AreaProperty);
}
public static void SetArea(DependencyObject obj, string value)
{
obj.SetValue(AreaProperty, value);
}
public static readonly DependencyProperty AreaProperty =
DependencyProperty.RegisterAttached("Area", typeof(string), typeof(UIElement), new PropertyMetadata(String.Empty, OnAreaChanged));
private static void OnAreaChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement child = d as UIElement;
if (child == null)
return;
Grid grid = VisualTreeHelper.GetParent(child) as Grid;
if (grid == null)
return;
AreaDefinitionCollection areas = GetAreaDefinitions(grid);
if (areas == null)
return;
// the performance of this bit could be improved by giving AreaDefinitionCollection a hash table implementation, oh well.
var areaDefinition = areas.FirstOrDefault(a => a.Name == e.NewValue.ToString());
if (areaDefinition == null)
return;
// update the grid elements
Grid.SetRow(child, areaDefinition.Row);
Grid.SetRowSpan(child, areaDefinition.RowSpan);
Grid.SetColumn(child, areaDefinition.Column);
Grid.SetColumnSpan(child, areaDefinition.ColumnSpan);
}
}
With this in place you can implement the functionality you're after, with some minor modifications:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<g:GridHelper.AreaDefinitions>
<g:AreaDefinition Row="0" Column="0" ColumnSpan="2" Name="Header" />
<g:AreaDefinition Row="1" Column="0" Name="Navigation" />
<g:AreaDefinition Row="1" Column="1" Name="Main" />
<g:AreaDefinition Row="2" Column="0" ColumnSpan="2" Name="Footer" />
</g:GridHelper.AreaDefinitions>
<TextBlock g:GridHelper.Area="Header" Text="Header" />
<TextBlock g:GridHelper.Area="Navigation" Text="Navigation" />
<TextBlock g:GridHelper.Area="Main" Text="Main" />
<TextBlock g:GridHelper.Area="Footer" Text="Footer" />
</Grid>
If you don't like the g: namespace prefix then you can get rid of it, as specified in this article, by adding the following line to your AssemblyInfo.cs file:
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "YourGridHelperNamespace")]
There isn't anything exactly like that, but there is a feature that could be used similarly. Styles are "a convenient way to apply a set of property values to multiple elements". Whever you have a number of elements which all need to have the same properties set to the same value, you can define a Style, like so:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.Resources>
<Style x:Key="AStyle" TargetType="TextBlock">
<Setter Property="Grid.Row" Value="1"/>
<Setter Property="Grid.Column" Value="1"/>
</Style>
</Grid.Resources>
<TextBlock Style="{StaticResource AStyle}" Text="Header" />
</Grid>
In your example code, this would be useless, because none of the TextBlocks share a complete set of values- you don't save any redundant code because it's only used once anyway. But a grid-area, if it existed, would be equally useless for the same reason.

Custom attached property doesn't work like Canvas.Left [duplicate]

I have problem with creating xaml control. I'm writing new project in VS 2015 in universal app. I want create grid. In this grid I want to have a button. In model I specifi the column (Level) and Row.
this is my code:
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=TechnologyList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="14*"/>
<ColumnDefinition Width="14*"/>
<ColumnDefinition Width="14*"/>
<ColumnDefinition Width="14*"/>
<ColumnDefinition Width="14*"/>
<ColumnDefinition Width="14*"/>
<ColumnDefinition Width="14*"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="Control">
<Setter Property="Grid.Column" Value="{Binding Level}" />
<Setter Property="Grid.Row" Value="{Binding Row}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I get a error in line <Setter Property="Grid.Column" Value="{Binding Level}" />
The error: Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED) was in edytor not in running code.
What is wrong? In "old" WPF everything was OK but in Universal App for Windows 10 I have a error.
Can anyone help me ?
As noted in the section Migration notes on the Setter.Value property page on MSDN, UWP/Windows Runtime does not support bindings in Style Setters.
Windows Presentation Foundation (WPF) and Microsoft Silverlight
supported the ability to use a Binding expression to supply the Value
for a Setter in a Style. The Windows Runtime doesn't support a Binding
usage for Setter.Value (the Binding won't evaluate and the Setter has
no effect, you won't get errors, but you won't get the desired result
either). When you convert XAML styles from WPF or Silverlight XAML,
replace any Binding expression usages with strings or objects that set
values, or refactor the values as shared {StaticResource} markup
extension values rather than Binding-obtained values.
A workaround could be a helper class with attached properties for the source paths of the bindings. It creates the bindings in code behind in a PropertyChangedCallback of the helper property:
public class BindingHelper
{
public static readonly DependencyProperty GridColumnBindingPathProperty =
DependencyProperty.RegisterAttached(
"GridColumnBindingPath", typeof(string), typeof(BindingHelper),
new PropertyMetadata(null, GridBindingPathPropertyChanged));
public static readonly DependencyProperty GridRowBindingPathProperty =
DependencyProperty.RegisterAttached(
"GridRowBindingPath", typeof(string), typeof(BindingHelper),
new PropertyMetadata(null, GridBindingPathPropertyChanged));
public static string GetGridColumnBindingPath(DependencyObject obj)
{
return (string)obj.GetValue(GridColumnBindingPathProperty);
}
public static void SetGridColumnBindingPath(DependencyObject obj, string value)
{
obj.SetValue(GridColumnBindingPathProperty, value);
}
public static string GetGridRowBindingPath(DependencyObject obj)
{
return (string)obj.GetValue(GridRowBindingPathProperty);
}
public static void SetGridRowBindingPath(DependencyObject obj, string value)
{
obj.SetValue(GridRowBindingPathProperty, value);
}
private static void GridBindingPathPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var propertyPath = e.NewValue as string;
if (propertyPath != null)
{
var gridProperty =
e.Property == GridColumnBindingPathProperty
? Grid.ColumnProperty
: Grid.RowProperty;
BindingOperations.SetBinding(
obj,
gridProperty,
new Binding { Path = new PropertyPath(propertyPath) });
}
}
}
You would use them in XAML like this:
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="local:BindingHelper.GridColumnBindingPath" Value="Level"/>
<Setter Property="local:BindingHelper.GridRowBindingPath" Value="Row"/>
</Style>
</ItemsControl.ItemContainerStyle>
For a simple workaround for absolute positioning (i.e. binding the Canvas.Left and canvas.Top properties), see this answer.
Wanted to add my experience of this BindingHelper idea from #clemens. It's a neat solution but I found that when targetting a ListViewItem the binding wouldn't access the underlying view model. After debugging it, I found that I needed to make sure the binding was relative to the ListViewItem itself and the associated .Content property to enable it to correctly link to the item's view model.
My particular use case was to set the IsTabStop property of the ListViewItem based on a view model value:
private static void BindingPathPropertyChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is string propertyPath)
{
var binding = new Binding
{
Path = new PropertyPath($"Content.{propertyPath}"),
Mode = BindingMode.OneWay,
RelativeSource = new RelativeSource
{
Mode = RelativeSourceMode.Self
}
};
BindingOperations.SetBinding(obj, Control.IsTabStopProperty, binding);
}
}
Hope this helps if anyone else has the problem.

DependencyProperty in UserControl not binding in ListItem Template

Ok hours of google searching and stakoverflow reading, I have been unable to find the answer to this issue.
I have a UserControl that is used to show a ProgressBar with a DependencyProperty of type double.
The MainPage.XAML.cs contains the DataContext:
void MainPage_Loaded(object sender,RoutedEventArgs e)
{
setDataContext();
MainGameListBox.ItemsSource = vm.GameList;
}
This is whats in the MainPage.XAML:
<ListBox Grid.Row="1" Grid.ColumnSpan="2" x:Name="MainGameListBox"
SelectionChanged="listBoxGameSearch_SelectionChanged" >
<!-- set its ItemsPanel to be a WrapPanel -->
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit1:WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<toolkit1:ContextMenuService.ContextMenu>
<toolkit1:ContextMenu>
<toolkit1:MenuItem Header="Pin to start" Click="PinGameToStart_Click" />
</toolkit1:ContextMenu>
</toolkit1:ContextMenuService.ContextMenu>
<Grid Width="173" Height="173"
Background="{StaticResource PhoneAccentBrush}" Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="86.5"/>
<ColumnDefinition Width="86.5"/>
</Grid.ColumnDefinitions>
<Border Grid.Row="0" Grid.RowSpan="3" Grid.Column="0" Width="64"
Height="64" BorderBrush="#70BC1F" BorderThickness="2"
Margin="6,6,0,0" VerticalAlignment="Top"
HorizontalAlignment="Left">
<Image Source="{Binding GameTile,
Converter={StaticResource imageCacheConverter}}" />
</Border>
<view:CircularProgressChart x:Name="circularProgChart"
Grid.Row="0" Grid.Column="1"
Grid.RowSpan="3" Grid.ColumnSpan="2"
Margin="6"
Loaded="CircularProgressChart_Loaded"
CompletionPercent="{Binding CompletionPercentage}" />
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The CompletionPercent is the DP and the UserControl is below:
public partial class CircularProgressChart:UserControl
{
public double CompletionPercent
{
get { return (double)GetValue(CompletionPercentProperty); }
set { SetValue(CompletionPercentProperty, value); }
}
public static readonly DependencyProperty CompletionPercentProperty = DependencyProperty.Register("CompletionPercent", typeof(double), typeof(CircularProgressChart), new PropertyMetadata(0.0, CompletionPercentChanged));
public CircularProgressChart()
{
try
{
InitializeComponent();
DataContext = this;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
Here's the CompletionPercentage Property:
public class Progress : INotifyPropertyChanged
{
private double _completionPercentage = 0.0;
public double CompletionPercentage
{
get{return _completionPercentage;}
set{
_completionPercentage = value;
NotifyPropertyChanged("CompletionPercentage");
}
}
protected void NotifyPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Question, is why is the
CompletionPercent="{Binding CompletionPercentage}"
not being bound? It gets the default value 0, but when the CompletionPercentage is updated the DP doesn't get the update. I've checked the NotifyPropertyChanged method and it fires correctly and works in all other parts of code.
The reason is DataContext = this line in CircularProgressChart constructor. {Binding CompletionPercentage} looks for CompletionPercentage property in DataContext, which obviously does not exist in CircularProgressChart. Instance of Progress class never makes it's way to the CircularProgressChart (explicit assignment of dependency property has a priority over binding).
Solution 1 (if you really want to keep dependency property): remove the DataContext = this; line. If you need to bind to properties of CircularProgressChart in it's XAML, specify correct binding sources instead of relying on DataContext.
Solution 2 (very common when deriving from UserControl): remove CompletionPercent dependency property completely (including its binding in MainPage.xaml) and bind to CompletionPercentage directly in CircularProgressChart.xaml. Also remove DataContext = this; line.
Solution 3 (true WPF-way): use default ProgressBar control with a custom template instead of creating your own control.

how to style Grid ColumnDefinitions in WPF

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>

Resources