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

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.

Related

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.

WPF Textbox is collapsed when the content string is a single digit

I am working on an application that creates textboxes/combo boxes dynamically based on if there is a single replacement for a keyword, or multiple replacements, and adds them to a stackpanel. I have ran into an issue where if the string that is being populated into the textbox is a single digit, ie: "2" the textbox is collapsed. Here is the associated DependencyProperty and constructor for the view model:
#region Properties
public ObservableCollection<string> KeywordValueList
{
get { return (ObservableCollection<string>)GetValue(_KeywordValueListProperty); }
set { SetValue(_KeywordValueListProperty, value); }
}
private static readonly DependencyProperty _KeywordValueListProperty = DependencyProperty.Register("KeywordValueList",
typeof(ObservableCollection<string>),
typeof(KeywordControlViewModel),
new PropertyMetadata(null, null));
public string KeywordValue
{
get { return (string)GetValue(_KeywordValueProperty); }
set { SetValue(_KeywordValueProperty, value); }
}
private static readonly DependencyProperty _KeywordValueProperty = DependencyProperty.Register("KeywordValue",
typeof(string),
typeof(KeywordControlViewModel),
new PropertyMetadata(null, null));
#endregion
#region Constructors
public KeywordControlViewModel(string keyword, object keywordValue)
{
Keyword = keyword;
if (keywordValue is string)
{
KeywordValue = (string)keywordValue;
KeywordValueList = null;
}
else if (keywordValue is ICollection)
{
KeywordValue = null;
ObservableCollection<string> toSet = new ObservableCollection<string>(keywordValue as List<string>);
KeywordValueList = toSet;
}
else
{
KeywordValue = "-Not Set-";
KeywordValueList = null;
}
}
#endregion
This is the relevant portion of the xaml:
<Grid HorizontalAlignment="Stretch">
<Grid.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Styles/ControlsStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<!-- Labels etc on Row 0, generating properly so omitting for space-->
<TextBox Name ="KeyWordTextBox"
Style="{StaticResource InputBoxStyle}"
Text="{Binding KeywordValue}"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
Visibility="{Binding KeywordValue, TargetNullValue=Hidden}">
<TextBox.ToolTip>
<Label Content="{Binding Keyword, StringFormat='Edit value for {0}'}" />
</TextBox.ToolTip>
</TextBox>
<ComboBox Name="KeyWordComboBox"
ItemsSource="{Binding KeywordValueList}"
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Visibility="{Binding KeywordValueList, TargetNullValue=Hidden}"/>
</Grid>
And the style:
<Style x:Key="InputBoxStyle" TargetType="TextBox">
<Setter Property="Margin" Value="0,0,5,0" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Height" Value="20" />
</Style>
I have debugged quite a bit and found that if I change the single digit to either a single letter, string of more than one number, or string containing both it shows the textbox as expected. Also, the Visibility value for the textbox in the debugger shows Collapsed, not Hidden -- the TargetNullValue does not seem to be causing this. In fact the textbox does not show either if I change it to Visible. This only started happening when I added the option for a combobox, prior to that the textbox generated properly with a single digit.
Can anyone offer an idea why this may be happening?
I suppose that the code doesn't work because You are binding a string value (and ObservableCollection<string> value in case of combobox) to a Visibility property.
In order to hide controls on null value:
You can write a value converter similar to the one suggested here: https://stackoverflow.com/a/2123905/232530 (but just check if value parameter is null in the Convert method)
or
You can use triggers as suggested here: How to hide the empty TextBlock?.
Please let me know if You need help when using any of those solutions.

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>

Render a custom attached property value

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);
}
}

Is there a color dialog for WPF?

I am looking for a color dialog for WPF?
Is there one built in?
Should I build my own ?
Or do Win 32 interop?
If so, how?
I wrote a simple WPF Color picker which supports the following features a while back
3 different types of color swatch
Opacity slider
Mouse mover control for selecting color
Sets to current color on open
Standard dialog buttons
Here is the article in case you need it : http://www.codeproject.com/Articles/33001/WPF-A-Simple-Color-Picker-With-Preview
Heres a simple color picker using attached properties, should anyone be looking for example code
public static class BrushExtender
{
public readonly static DependencyProperty BrushProperty = DependencyProperty.RegisterAttached("Brush", typeof(Brush), typeof(BrushExtender), new PropertyMetadata(Brushes.Black,DoBrushChanged));
public readonly static DependencyProperty RedChannelProperty = DependencyProperty.RegisterAttached("RedChannel", typeof(int), typeof(BrushExtender), new PropertyMetadata(DoColorChangedRed));
public readonly static DependencyProperty GreenChannelProperty = DependencyProperty.RegisterAttached("GreenChannel", typeof(int), typeof(BrushExtender), new PropertyMetadata(DoColorChangedGreen));
public readonly static DependencyProperty BlueChannelProperty = DependencyProperty.RegisterAttached("BlueChannel", typeof(int), typeof(BrushExtender), new PropertyMetadata(DoColorChangedBlue));
public readonly static DependencyProperty AlphaChannelProperty = DependencyProperty.RegisterAttached("AlphaChannel", typeof(int), typeof(BrushExtender), new PropertyMetadata(DoColorChangedAlpha));
public readonly static DependencyProperty ColourValueProperty = DependencyProperty.RegisterAttached("ColourValue", typeof(string), typeof(BrushExtender), new PropertyMetadata(DoValueChanged));
public static void SetRedChannel(DependencyObject o, int value)
{
o.SetValue(RedChannelProperty, value);
}
public static void SetGreenChannel(DependencyObject o, int value)
{
o.SetValue(GreenChannelProperty, value);
}
public static void SetBlueChannel(DependencyObject o, int value)
{
o.SetValue(BlueChannelProperty, value);
}
public static void SetAlphaChannel(DependencyObject o, int value)
{
o.SetValue(AlphaChannelProperty, value);
}
public static void SetBrush(DependencyObject o, SolidColorBrush brush)
{
o.SetValue(BrushProperty, brush);
}
public static void SetColourValue(DependencyObject o, string value)
{
o.SetValue(ColourValueProperty, value);
}
private static void DoColorChangedRed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
DoColorChange(d, (int)e.NewValue, c => c.R, () => Color.FromArgb(color.A, ((byte)(int)e.NewValue), color.G , color.B));
}
private static void DoColorChangedGreen(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
DoColorChange(d, (int)e.NewValue, c => c.G, () => Color.FromArgb(color.A, color.R, ((byte)(int)e.NewValue), color.B));
}
private static void DoColorChangedBlue(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
DoColorChange(d, (int)e.NewValue, c => c.B, () => Color.FromArgb(color.A, color.R, color.G, (byte)(int)e.NewValue));
}
private static void DoColorChangedAlpha(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
DoColorChange(d, (int)e.NewValue, c => c.A, () => Color.FromArgb((byte)(int)e.NewValue, color.R, color.G, color.B));
}
private static void DoColorChange(DependencyObject d, int newValue, Func<Color, int> colorCompare, Func<Color> getColor)
{
var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
if (colorCompare(color) == newValue)
return;
var newBrush = new SolidColorBrush(getColor());
d.SetValue(BrushProperty, newBrush);
d.SetValue(ColourValueProperty, newBrush.Color.ToString());
}
private static void DoValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
if (color.ToString() == (string)e.NewValue)
return;
Color? newColour = null;
try
{
newColour = (Color) ColorConverter.ConvertFromString((string) e.NewValue);
}
catch{}
if (newColour == null)
return;
var newBrush = new SolidColorBrush(newColour.Value);
d.SetValue(BrushProperty, newBrush);
}
private static void DoBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == e.OldValue)
return;
var colour = ((SolidColorBrush)e.NewValue).Color;
d.SetValue(RedChannelProperty,(int)colour.R);
d.SetValue(GreenChannelProperty,(int)colour.G);
d.SetValue(BlueChannelProperty,(int)colour.B);
d.SetValue(AlphaChannelProperty,(int)colour.A);
d.SetValue(ColourValueProperty,colour.ToString());
}
}
and here it is being used
<Window x:Class="ChannelColourBrush.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:c="clr-namespace:ChannelColourBrush" Title="Window1" Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="Red" />
<TextBlock Text="Green" Grid.Row="1" />
<TextBlock Text="Blue" Grid.Row="2" />
<TextBlock Text="Alpha" Grid.Row="3" />
<Slider Name="redSlider" Grid.Column="1" Minimum="0" Maximum="255" Width="200" Height="20" Grid.ColumnSpan="2" Value="{Binding ElementName=rect, Path=(c:BrushExtender.RedChannel), Mode=TwoWay}" />
<Slider Name="greenSlider" Grid.Column="1" Grid.Row="1" Minimum="0" Maximum="255" Width="200" Height="20" Grid.ColumnSpan="2" Value="{Binding ElementName=rect, Path=(c:BrushExtender.GreenChannel), Mode=TwoWay}" />
<Slider Name="blueSlider" Grid.Column="1" Grid.Row="2" Minimum="0" Maximum="255" Width="200" Height="20" Grid.ColumnSpan="2" Value="{Binding ElementName=rect, Path=(c:BrushExtender.BlueChannel), Mode=TwoWay}" />
<Slider Name="alphaSlider" Grid.Column="1" Grid.Row="3" Minimum="0" Maximum="255" Width="200" Height="20" Grid.ColumnSpan="2" Value="{Binding ElementName=rect, Path=(c:BrushExtender.AlphaChannel), Mode=TwoWay}" />
<Rectangle Fill="SandyBrown" Name="rect" Width="200" Height="50" Grid.Row="4" Grid.ColumnSpan="3" Margin="0,20,0,10"
c:BrushExtender.Brush="{Binding RelativeSource={RelativeSource Self}, Path=Fill, Mode=TwoWay}"/>
<TextBlock Text="Colour Value" Margin="5,0,5,0" Grid.Row="5" HorizontalAlignment="Center" />
<TextBox Text="{Binding ElementName=rect, Path=(c:BrushExtender.ColourValue), Mode=TwoWay}" Margin="0,0,0,0" Grid.Row="5" Grid.Column="1" Width="100" HorizontalAlignment="Center" />
<Button Content="Update" IsEnabled="{Binding ElementName=grid, Path=SelectedItem.SomeValue}"/>
</Grid>
You can easily use the ColorDialog available from classic Windows Forms.
using System.Windows.Forms;
ColorDialog colorDialog = new ColorDialog();
if (colorDialog.ShowDialog() == DialogResult.OK)
{
// do some stuff with colors...
}
I was forced to make such dialog myself. Please look at WPFColorLib - it's FOSS.

Resources