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>
Related
I'm relatively new to WPF animations. I would like to have an object (a simple circle for example) move around a circular path in 10 degree increments. The following example is tantalizingly close: WPF circular moving object See the answer by Clemens who uses a RotateTransform in XAML and a DoubleAnimation in code-behind. I am however, using MVVM and it's not clear to me how to accomplish this. I believe I would need to access the RotateTransform which is in the View from my ViewModel, so I can call BeginAnimation, but how?
Any examples or ideas? I have searched without luck.
Thanks
UPDATE: I can now be more specific in my question by showing what I've already tried. Based on the above mentioned reference AND Twoway-bind view's DependencyProperty to viewmodel's property? (answer by #Michael Schnerring), I have the following simple code (below). My ellipse is not rotating. No binding errors, or any other errors, just no rotation. And my methods are hit (I debugged into it) I'm guessing my PerformAnimation function is incorrect, specifically the SetTargetProperty part. I did try to play with it by adding two animations (one for Rotation, one for Transform) but without luck.
Can someone give me an idea what I'm doing wrong?
XAML:
<Canvas Grid.Row="0" Grid.Column="0">
<Ellipse Height="100" Width="100" Fill="Aqua" Name="MyEllipse"
Canvas.Left="200" Canvas.Top="200"
RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TransformGroup>
<TranslateTransform Y="-100"/>
<RotateTransform />
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
</Canvas>
<Button Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Command="{Binding Path=RotateCommand}">Press Me!</Button>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var nameOfPropertyInVm = "AngleProperty";
var binding = new Binding(nameOfPropertyInVm) { Mode = BindingMode.TwoWay };
this.SetBinding(AngleProperty, binding);
}
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
// Using a DependencyProperty as the backing store for Angle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AngleProperty = DependencyProperty.Register("Angle", typeof(double), typeof(MainWindow), new UIPropertyMetadata(0.0, new PropertyChangedCallback(AngleChanged)));
private static void AngleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MainWindow control = (MainWindow)sender;
control.PerformAnimation((double)e.OldValue, (double)e.NewValue);
}
private void PerformAnimation(double oldValue, double newValue)
{
Storyboard s = new Storyboard();
DoubleAnimation animation = new DoubleAnimation();
animation.From = oldValue;
animation.To = newValue;
animation.Duration = new Duration(TimeSpan.FromSeconds(1));
s.Children.Add(animation);
Storyboard.SetTarget(animation, MyEllipse);
Storyboard.SetTargetProperty(animation, new PropertyPath("(Ellipse.RenderTransform).(RotateTransform.Angle)"));
s.Begin();
}
And ViewModel
public class MyViewModel : ViewModelBase
{
private ICommand _rotateCommand = null;
private double _angleProperty = 10;
public MyViewModel()
{
}
public double AngleProperty
{
get
{
return _angleProperty;
}
set
{
_angleProperty = value;
OnPropertyChanged("AngleProperty");
}
}
public ICommand RotateCommand
{
get
{
if (_rotateCommand == null)
{
_rotateCommand = new RelayCommand(param => RotateCommandImplementation());
}
return _rotateCommand;
}
}
private void RotateCommandImplementation()
{
AngleProperty = AngleProperty + 10;
}
}
Here's my solution, based on a lot of help from #Clemens (see comments and WPF circular moving object)
VIEW
<Window x:Class="AnimationBindingPlay.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:AnimationBindingPlay"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Canvas Grid.Row="0" Grid.Column="0">
<Ellipse Height="100" Width="100" Fill="Aqua" Name="MyEllipse"
Canvas.Left="200" Canvas.Top="200"
RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TransformGroup>
<TranslateTransform Y="-100"/>
<RotateTransform x:Name="rotateTransform"/>
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
</Canvas>
<Button Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Command="{Binding Path=RotateCommand}">Press Me!</Button>
</Grid>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var nameOfPropertyInVm = "AngleProperty";
var binding = new Binding(nameOfPropertyInVm) { Mode = BindingMode.TwoWay };
this.SetBinding(AngleProperty, binding);
}
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
// Using a DependencyProperty as the backing store for Angle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AngleProperty = DependencyProperty.Register("Angle", typeof(double), typeof(MainWindow), new UIPropertyMetadata(0.0, new PropertyChangedCallback(AngleChanged)));
private static void AngleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MainWindow control = (MainWindow)sender;
control.PerformAnimation((double)e.OldValue, (double)e.NewValue);
}
private void PerformAnimation(double oldValue, double newValue)
{
var rotationAnimation = new DoubleAnimation(oldValue, newValue, TimeSpan.FromSeconds(1));
rotateTransform.BeginAnimation(RotateTransform.AngleProperty, rotationAnimation);
}
}
ViewModel
Same as in question!
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.
I have a Grid having two columns. One column is twice the width as other.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="12"/>
<TextBox Grid.Column="1" Text="" />
</Grid>
But during runtime when i type in second text box the width changes without maintaining the 1:2 ratio.
I dont want to keep fixed width. The width will be changed at runtime based on user entry.
I want the width to be in 1:2 ratio even if the width changes at runtime?
Here is another, alternative answer that you can modify into your needs.
In this example, you can enter the width of your grid in the textbox in the first column. Or you can expand or decrease the width with the button. Just for illustration. You may have to change this for your purpose.
MainWindow.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
public static readonly DependencyProperty GridWidthProperty = DependencyProperty.Register("GridWidth", typeof(Double), typeof(MainWindow), new UIPropertyMetadata(300d, OnGridWidthPropertyChanged));
public Double GridWidth
{
get { return (Double)GetValue(GridWidthProperty); }
set
{
SetValue(GridWidthProperty, value);
NotifyPropertyChanged("GridWidth");
}
}
public static readonly DependencyProperty ColumnWidthProperty = DependencyProperty.Register("ColumnWidth", typeof(String), typeof(MainWindow), new UIPropertyMetadata("100", OnColumnWidthPropertyChanged));
public String ColumnWidth
{
get { return (String)GetValue(ColumnWidthProperty); }
set
{
SetValue(ColumnWidthProperty, value);
NotifyPropertyChanged("ColumnWidth");
}
}
private static void OnGridWidthPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
MainWindow ctl = sender as MainWindow;
ctl.doGridWidthChanged();
ctl = null;
}
private static void OnColumnWidthPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
MainWindow ctl = sender as MainWindow;
ctl.doColumnWidthChanged();
ctl = null;
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private void button_Click(object sender, RoutedEventArgs e)
{
if (sender == button1)
this.GridWidth += 50;
else if (sender == button2)
this.GridWidth -= 50;
}
private void doGridWidthChanged()
{
if (Double.IsNaN(this.GridWidth))
return;
this.ColumnWidth = Math.Round((this.GridWidth / 3), 2).ToString();
}
private void doColumnWidthChanged()
{
Double columnwidthval = Double.NaN;
if (!String.IsNullOrEmpty(this.ColumnWidth) && Double.TryParse(this.ColumnWidth, out columnwidthval))
this.GridWidth = columnwidthval * 3;
else
this.ColumnWidth = Math.Round((this.GridWidth / 3), 2).ToString();
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(String PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
And here is my XAML code.
MainWindow.xaml
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication3"
Title="MainWindow" Height="600" Width="800">
<Grid>
<Grid Margin="0,60,0,0"
Width="{Binding Path=GridWidth}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="GhostWhite" />
<Border Grid.Column="1" Background="AliceBlue" />
<Border Grid.ColumnSpan="2" BorderBrush="DimGray" BorderThickness="1" />
<StackPanel Grid.Column="0" Orientation="Vertical" Margin="3">
<TextBlock Text="Single" />
<TextBox Text="{Binding Path=ColumnWidth, Mode=TwoWay}" />
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Vertical" Margin="3">
<TextBlock Text="Double" />
</StackPanel>
</Grid>
<Button Content="Increase" Height="34" HorizontalAlignment="Left" Margin="19,12,0,0" Name="button1" VerticalAlignment="Top" Width="90" Click="button_Click" />
<Button Content="Decrease" Height="34" HorizontalAlignment="Left" Margin="120,12,0,0" Name="button2" VerticalAlignment="Top" Width="90" Click="button_Click" />
</Grid>
</Window>
Hope that helps!
This can be done like this:
MainWindow.xaml
<Grid>
<Grid Width="{Binding Path=GridWidth}"
Margin="0,60,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="Single 33%"/>
<TextBox Grid.Column="1" Text="Double 67%" />
</Grid>
<Button Content="Increase" Height="34" HorizontalAlignment="Left" Margin="19,12,0,0" Name="button1" VerticalAlignment="Top" Width="90" Click="button_Click" />
<Button Content="Decrease" Height="34" HorizontalAlignment="Left" Margin="120,12,0,0" Name="button2" VerticalAlignment="Top" Width="90" Click="button_Click" />
</Grid>
I have two buttons that increase or decrease the total grid width for illustration.
MainWindows.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
public static readonly DependencyProperty GridWidthProperty = DependencyProperty.Register("GridWidth", typeof(Double), typeof(MainWindow), new UIPropertyMetadata(300d));
public Double GridWidth
{
get { return (Double)GetValue(GridWidthProperty); }
set
{
SetValue(GridWidthProperty, value);
NotifyPropertyChanged("GridWidth");
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private void button_Click(object sender, RoutedEventArgs e)
{
if (sender == button1)
this.GridWidth += 50;
else if (sender == button2)
this.GridWidth -= 50;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(String PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
i have made a template that look like this :
<ControlTemplate x:Key="onoffValue" TargetType="{x:Type Control}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Height="20" Margin="0,5,0,0">
<RadioButton Content="On" Height="20" Name="On_radiobutton" />
<RadioButton Content="Off" Height="20" Name="Off_radiobutton" Margin="20,0,0,0" />
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=BootSector}" Value="true">
<Setter TargetName="On_radiobutton" Property="IsChecked" Value="true"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=BootSector}" Value="false">
<Setter TargetName="Off_radiobutton" Property="IsChecked" Value="true"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
For now, it is bind to the property BootSector(bool) ofa "Configuration" object.
I use this template in my window that has a configuration object as data context like this :
<Control Template="{StaticResource onoffValue}">
</Control>
It works great, but i want to go further.
I would like to know how i can pass a different property to my template to dynamically bind (dynamically change the property the template is bind to)
ie i tryed something like
<Control Template="{StaticResource onoffValue}" xmlns:test="{Binding Path=BootSector}"/>
and bind it in the template to "test" but it doesn't work
Is it possible ? How can i do that ? I think i'm not too far away but not there still !
Thank you in advance
Edit : Concerning Dmitry answer :
There is a bug using that. When i do :
<StackPanel local:ToggleControl.IsOn="{Binding BootSector, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal" HorizontalAlignment="Center" Margin="5">
<RadioButton Content="On" local:ToggleControl.Role="On" Height="20" Margin="5" />
<RadioButton Content="Off" local:ToggleControl.Role="Off" Height="20" Margin="5" />
</StackPanel>
By default BootSector is on false. When i click on the on button (true), it sets bootSector to true and then immediately to false . The behaviour should be that it stays to true until it is unchecked ? Is this related to the problem related here ? http://geekswithblogs.net/claraoscura/archive/2008/10/17/125901.aspx
Here, the idea is - generic behaviors are never complex and generally not worth creating a custom control. I undertand that implmentation may vary, but the approach will remain the same. It makes sense to use XAML for the parts which can change and code for the stuff which will remain constant.
UPDATE 1- It's getting even easier when using Custom controls. You won't need attached property no more - as you'll get a dedicated space for it inside your custom control, also, you can use x:Name and GetTemplateChild(..) to otain a reference to individual RadioButtons.
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace RadioButtons
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += (o, e) =>
{
this.DataContext = new TwoBoolean()
{
PropertyA = false,
PropertyB = true
};
};
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(((TwoBoolean)this.DataContext).ToString());
}
}
public enum RadioButtonRole
{
On,
Off
}
public class ToggleControl : DependencyObject
{
public static readonly DependencyProperty IsOnProperty =
DependencyProperty.RegisterAttached("IsOn",
typeof(bool?),
typeof(ToggleControl),
new PropertyMetadata(null,
new PropertyChangedCallback((o, e) =>
{
ToggleControl.OnIsOnChanged((Panel)o, (bool)e.NewValue);
})));
public static readonly DependencyProperty RoleProperty =
DependencyProperty.RegisterAttached("Role",
typeof(RadioButtonRole?),
typeof(ToggleControl),
new PropertyMetadata(null,
new PropertyChangedCallback((o, e) =>
{
})));
private static readonly DependencyProperty IsSetUpProperty =
DependencyProperty.RegisterAttached("IsSetUp",
typeof(bool),
typeof(ToggleControl),
new PropertyMetadata(false));
private static void OnIsOnChanged(Panel panel, bool e)
{
if (!ToggleControl.IsSetup(panel))
{
ToggleControl.Setup(panel);
}
RadioButtonRole role;
if (e)
{
role = RadioButtonRole.On;
}
else
{
role = RadioButtonRole.Off;
}
ToggleControl.GetRadioButtonByRole(role, panel).IsChecked = true;
}
private static void Setup(Panel panel)
{
// get buttons
foreach (RadioButton radioButton in
new RadioButtonRole[2]
{
RadioButtonRole.On,
RadioButtonRole.Off
}.Select(t =>
ToggleControl.GetRadioButtonByRole(t, panel)))
{
radioButton.Checked += (o2, e2) =>
{
RadioButton checkedRadioButton = (RadioButton)o2;
panel.SetValue(ToggleControl.IsOnProperty,
ToggleControl.GetRadioButtonRole(checkedRadioButton) == RadioButtonRole.On);
};
}
panel.SetValue(ToggleControl.IsSetUpProperty, true);
}
private static bool IsSetup(Panel o)
{
return (bool)o.GetValue(ToggleControl.IsSetUpProperty);
}
private static RadioButton GetRadioButtonByRole(RadioButtonRole role,
Panel container)
{
return container.Children.OfType<RadioButton>().First(t =>
(RadioButtonRole)t.GetValue(ToggleControl.RoleProperty) == role);
}
private static RadioButtonRole GetRadioButtonRole(RadioButton radioButton)
{
return (RadioButtonRole)radioButton.GetValue(ToggleControl.RoleProperty);
}
public static void SetIsOn(DependencyObject o, bool? e)
{
o.SetValue(ToggleControl.IsOnProperty, e);
}
public static bool? GetIsOn(DependencyObject e)
{
return (bool?)e.GetValue(ToggleControl.IsOnProperty);
}
public static void SetRole(DependencyObject o, RadioButtonRole? e)
{
o.SetValue(ToggleControl.RoleProperty, e);
}
public static RadioButtonRole? GetRole(DependencyObject e)
{
return (RadioButtonRole?)e.GetValue(ToggleControl.RoleProperty);
}
}
public class TwoBoolean: INotifyPropertyChanged
{
private bool propertyA, propertyB;
public bool PropertyA
{
get
{
return this.propertyA;
}
set
{
this.propertyA = value;
this.OnPropertyChanged("PropertyA");
}
}
public bool PropertyB
{
get
{
return this.propertyB;
}
set
{
this.propertyB = value;
this.OnPropertyChanged("PropertyB");
}
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
public override string ToString()
{
return string.Format("PropertyA:{0}, PropertyB:{1}", this.PropertyA, this.PropertyB);
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Markup:
<Window x:Class="RadioButtons.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RadioButtons"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Margin="5" VerticalAlignment="Center">PropertyA</TextBlock>
<StackPanel local:ToggleControl.IsOn="{Binding PropertyA, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal" HorizontalAlignment="Center" Margin="5">
<RadioButton Content="On" local:ToggleControl.Role="On" Height="20" Margin="5" />
<RadioButton Content="Off" local:ToggleControl.Role="Off" Height="20" Margin="5" />
</StackPanel>
<TextBlock Grid.Row="1" Grid.Column="0" Margin="5" VerticalAlignment="Center">PropertyB</TextBlock>
<StackPanel local:ToggleControl.IsOn="{Binding PropertyB, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Grid.Row="1" Grid.Column="1"
Orientation="Horizontal" HorizontalAlignment="Center" Margin="5">
<RadioButton Content="On" local:ToggleControl.Role="On" Height="20" Margin="5" />
<RadioButton Content="Off" local:ToggleControl.Role="Off" Height="20" Margin="5" />
</StackPanel>
<Button Click="Button_Click" Grid.Row="3" Grid.ColumnSpan="2">Save</Button>
</Grid>
</Window>
You should not use an xmlns to pass a parameter, rather use the Tag or template a ContentControl, then you can bind the Content to your property (set it to TwoWay) and use a TemplateBinding to Content inside the template.
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);
}
}