When I create grid with row or column, if I don't specify width/height, column/row adjust it's height/width to it's content like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="TestBtn"
Height="50"
Width="100"/>
</Grid>
Row set it's height to 50.
Question: How to return row/column to that default behavior after custom setting the width/height in code? Like this:
public void SetRowHeight(double height, bool setDefault)
{
if (setDefault)
{
Grid0Row1.DefaultHeightBehaivor = true;
}
else
{
Grid0Row1.Height = new GridLength(Height);
}
}
For that, you need to use new GridLength() with empty parameters and code will look like this:
public void SetRowHeight(double height, bool setDefault)
{
if (setDefault)
{
Grid0Row1.Height = new GridLength();
}
else
{
Grid0Row1.Height = new GridLength(height);
}
}
Related
I have a liveChart and am creating checkboxes for each item in a list. This list also has data for each series in liveCharts. How do I bind my dynamically created checkboxes with each individual LiveCharts.LineSeries from my data?
I've created the checkboxes:
<!-- Creating checkboxes by binding to list -->
<ListView ItemsSource="{Binding ElementItemList}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" Width="600">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=ElementName}" />
<CheckBox IsChecked="{Binding Path=ElementIsSelected}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
<!-- Display the chart -->
<Grid Grid.Row="1" x:Name="TestGrid"></Grid>
So I assume that you want to have a CheckBox representing each LineSeries in your SeriesCollection.
So I would have two public properties, one for the SeriesCollection and the other for the list of CheckBox controls.
public SeriesCollection SeriesCollection { get; set; }
public List<CheckBox> CheckBoxCollection { get; set; }
Then following is a function that mimics dynamically creating your LineSeries and CheckBox controls since you didn't provide that code. It is important to have some sort of a connection between the CheckBox controls and your line series, and in this case I decided to set LineSeries.Title and CheckBox.Name the same.
Also note that in order to have the CheckBox do something upon checking/unchecking, you'd need to register two events for each.
public void DynamicallyCreateStuff()
{
SeriesCollection = new SeriesCollection();
CheckBoxCollection = new List<CheckBox>();
var count = 3;
var val1 = new List<double>() { 1, 2, 3 };
var val2 = new List<double>() { 9, 5, 3 };
var val3 = new List<double>() { 1, 4, 9 };
for (int i = 1; i <= count; i++)
{
var name = string.Format("LineSeries{0}", i);
var checkBox = new CheckBox
{
Name = name,
Content = name,
Margin = new Thickness() { Left = 8, Top = 8, Right = 8, Bottom = 8 },
IsChecked = true
};
checkBox.Checked += DynamicCheckBoxChecked;
checkBox.Unchecked += DynamicCheckBoxUnchecked;
CheckBoxCollection.Add(checkBox);
var lineSeries = new LineSeries
{
Title = name
};
if (i == 1)
{
lineSeries.Values = new ChartValues<double>(val1);
}
else if (i == 2)
{
lineSeries.Values = new ChartValues<double>(val2);
}
else if (i == 3)
{
lineSeries.Values = new ChartValues<double>(val3);
}
SeriesCollection.Add(lineSeries);
}
}
In my case, I decided to have the corresponding series become visible/hidden upon clicking the CheckBox, so my check/uncheck methods look like this:
private void DynamicCheckBoxChecked(object sender, EventArgs e)
{
ShowHideSeries(sender, Visibility.Visible);
}
private void DynamicCheckBoxUnchecked(object sender, EventArgs e)
{
ShowHideSeries(sender, Visibility.Collapsed);
}
private void ShowHideSeries(object sender, Visibility visibility)
{
var checkBox = (CheckBox)sender;
var found = SeriesCollection.FirstOrDefault(x => x.Title == checkBox.Name);
if (found != null)
{
var series = (LineSeries)found;
series.Visibility = visibility;
}
}
I didn't use a ViewModel in order to save time and for the sake of simplicity, so my MainWindow constructor looks like this:
public MainWindow()
{
InitializeComponent();
DynamicallyCreateStuff();
DataContext = this;
}
And XAML is pretty bare bones here:
<Window x:Class="SOLineCharts.MainWindow"
....
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ItemsControl Grid.Column="0"
ItemsSource="{Binding CheckBoxCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" ScrollViewer.HorizontalScrollBarVisibility="Disabled"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<lvc:CartesianChart Series="{Binding SeriesCollection}" Grid.Column="1"/>
</Grid>
</Window>
Result:
Upon loading:
Unchecking one check box:
I working on a simple imageviewer app. I control the Stretch property on the binding based on ViewModel property.
The problem occurs when I change the Stretch attribute based on a 'Combobox', bound to ViewModel, and the image 'cuts off' the corners of a wide image when using 'UniformToFill'. Hence to use of a ScrollViewer to be able to scroll the image content.
The problem is the ScrollViewer doesn't seem to show up scrollbars for me to be able to scroll.
WPF Markup:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Other Grids removed -->
<Grid Name="Container" Grid.Column="2" Grid.Row="0" Grid.RowSpan="2">
<ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<Image Source="{Binding SelectedPhoto.Value.Image}"
Stretch="{Binding ImageStretch}" Name="PhotoImage" />
</ScrollViewer>
</Grid>
I understand if I set a fixed Height and Width to ScrollViewer and Image, it will work. But I want to do it Dynamically:
The ScrollView Will have Height and Width from Parent 'Grid(Contaioner)' Control.
The Image will have Height and Width from itself, but take Stretch to account in that calculation.
Possible to solve with ActualHeight, ActualWidth? And a DependecyProperty?
This is almost impossible, Or I should say it doesn't make a lot of sense to expect ScrollViewer to know the boundaries of an image with Stretch = UniformToFill. According to MSDN:
UniformToFill:
The content (your Image) is resized to fill the destination dimensions (window or grid) while it
preserves its native aspect ratio. If the aspect ratio of the
destination rectangle differs from the source, the source content is
clipped to fit in the destination dimensions (Therefore the image will be cutted off).
So I think what we really need here is to use Uniform + Proper Scaling instead of UniformToFill.
The solution is when Stretch is set to UniformToFill it must set to Uniform and then Image.Width = image actual width * scalingParam and Image.Height= image actual height * scalingParam, where scalingParam = Grid.Width (or Height) / image actual width (or Height). This way ScrollViewer boundaries will be the same as the image scaled size.
I've provided a working solution to give you an Idea, I'm not sure how suitable would it be for your case but here it is:
First I defined a simple view-model for my Images:
public class ImageViewModel: INotifyPropertyChanged
{
// implementation of INotifyPropertyChanged ...
private BitmapFrame _bitmapFrame;
public ImageViewModel(string path, Stretch stretch)
{
// determining the actual size of the image.
_bitmapFrame = BitmapFrame.Create(new Uri(path), BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
Width = _bitmapFrame.PixelWidth;
Height = _bitmapFrame.PixelHeight;
Scale = 1;
Stretch = stretch;
}
public int Width { get; set; }
public int Height { get; set; }
double _scale;
public double Scale
{
get
{
return _scale;
}
set
{
_scale = value;
OnPropertyChanged("Scale");
}
}
Stretch _stretch;
public Stretch Stretch
{
get
{
return _stretch;
}
set
{
_stretch = value;
OnPropertyChanged("Stretch");
}
}
}
In the above code BitmapFrame is used to determine the actual size of the image.
Then I did some initializations in my Mainwindow (or main view-model):
// currently displaying image
ImageViewModel _imageVm;
public ImageViewModel ImageVM
{
get
{
return _imageVm;
}
set
{
_imageVm = value;
OnPropertyChanged("ImageVM");
}
}
// currently selected stretch type
Stretch _stretch;
public Stretch CurrentStretch
{
get
{
return _stretch;
}
set
{
_stretch = value;
//ImageVM should be notified to refresh UI bindings
ImageVM.Stretch = _stretch;
OnPropertyChanged("ImageVM");
OnPropertyChanged("CurrentStretch");
}
}
// a list of Stretch types
public List<Stretch> StretchList { get; set; }
public string ImagePath { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
// sample image path
ImagePath = #"C:\Users\...\YourFile.png";
StretchList = new List<Stretch>();
StretchList.Add( Stretch.None);
StretchList.Add( Stretch.Fill);
StretchList.Add( Stretch.Uniform);
StretchList.Add( Stretch.UniformToFill);
ImageVM = new ImageViewModel(ImagePath, Stretch.None);
CurrentStretch = StretchList[0];
}
My Xaml looks like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" >
<Grid.Resources>
<local:MultiConverter x:Key="multiC"/>
</Grid.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<Image Source="{Binding ImagePath}" Name="PhotoImage">
<Image.Stretch>
<MultiBinding Converter="{StaticResource multiC}">
<Binding Path="ImageVM" />
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="ActualWidth"/>
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="ActualHeight"/>
</MultiBinding>
</Image.Stretch>
<Image.LayoutTransform>
<ScaleTransform ScaleX="{Binding ImageVM.Scale}" ScaleY="{Binding ImageVM.Scale}"
CenterX="0.5" CenterY="0.5" />
</Image.LayoutTransform>
</Image>
</ScrollViewer>
</Grid>
<ComboBox Grid.Row="2" Grid.Column="0" ItemsSource="{Binding StretchList}" SelectedItem="{Binding CurrentStretch}" DisplayMemberPath="."/>
</Grid>
As you can see, I've used a multi-value converter that takes 3 arguments: current image view-model and window width and height. This arguments were used to calculate current size of the area that image fills. Also I've used ScaleTransform to scale that area to the calculated size. This is the code for multi-value converter:
public class MultiConverter : IMultiValueConverter
{
public object Convert(
object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] is ImageViewModel)
{
var imageVm = (ImageViewModel)values[0];
// if user selects UniformToFill
if (imageVm.Stretch == Stretch.UniformToFill)
{
var windowWidth = (double)values[1];
var windowHeight = (double)values[2];
var scaleX = windowWidth / (double)imageVm.Width;
var scaleY = windowHeight / (double)imageVm.Height;
// since it's "uniform" Max(scaleX, scaleY) is used for scaling in both horizontal and vertical directions
imageVm.Scale = Math.Max(scaleX, scaleY);
// "UniformToFill" is actually "Uniform + Proper Scaling"
return Stretch.Uniform;
}
// if user selects other stretch types
// remove scaling
imageVm.Scale = 1;
return imageVm.Stretch;
}
return Binding.DoNothing;
}
public object[] ConvertBack(
object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
So ultimately i took a discussion with some co-workers and we agreed that we need to fix the problem before a fix. In other words replace Stretch attribute combined with scrollviewer with something more robust that will support extent ability.
The solution I came up with will work for now, and a better solution to the whole problem will be preformed next scrum sprint.
Solution
A custom dependencyproperty that will control width and height depending on stretch attribute currently present on element.
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Column="2" Grid.Row="0" Grid.RowSpan="2">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Image Name="PhotoImage"
Source="{Binding SelectedPhoto.Value.Image}"
Stretch="{Binding ImageStretch, NotifyOnTargetUpdated=True}}"
extensions:ImageExtensions.ChangeWidthHeightDynamically="True"/>
</ScrollViewer>
</Grid>
Dependency Property
public static bool GetChangeWidthHeightDynamically(DependencyObject obj)
{
return (bool)obj.GetValue(ChangeWidthHeightDynamicallyProperty);
}
public static void SetChangeWidthHeightDynamically(DependencyObject obj, bool value)
{
obj.SetValue(ChangeWidthHeightDynamicallyProperty, value);
}
public static readonly DependencyProperty ChangeWidthHeightDynamicallyProperty =
DependencyProperty.RegisterAttached("ChangeWidthHeightDynamically", typeof(bool), typeof(ImageExtensions), new PropertyMetadata(false, OnChangeWidthHeightDynamically));
private static void OnChangeWidthHeightDynamically(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var image = d as Image;
if (image == null)
return;
image.SizeChanged += Image_SizeChanged;
image.TargetUpdated += Updated;
}
private static void Updated(object sender, DataTransferEventArgs e)
{
//Reset Width and Height attribute to Auto when Target updates
Image image = sender as Image;
if (image == null)
return;
image.Width = double.NaN;
image.Height = double.NaN;
}
private static void Image_SizeChanged(object sender, SizeChangedEventArgs e)
{
var image = sender as Image;
if (image == null)
return;
image.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
if (Math.Abs(image.ActualHeight) <= 0 || Math.Abs(image.ActualWidth) <= 0)
return;
switch (image.Stretch)
{
case Stretch.Uniform:
{
image.Width = Double.NaN;
image.Height = Double.NaN;
break;
}
case Stretch.None:
{
image.Width = image.RenderSize.Width;
image.Height = image.RenderSize.Height;
break;
}
case Stretch.UniformToFill:
{
image.Width = image.ActualWidth;
image.Height = image.ActualHeight;
break;
}
default:
{
image.Width = double.NaN;
image.Height = double.NaN;
break;
}
}
}
The problem may come from the rest of your layout - If the Grid is contained in an infinitely resizable container (a Grid Column/Row set to Auto, a StackPanel, another ScrollViewer...), it will grow with the Image. And so will do the ScrollViewer, instead of activating the scroll bars.
When I create ListBox with virtualization enabled and then update all its items appearance it works very fast. But when i slowly scroll down all items in ListBox and then update all items appearance it takes a lot of time. I think it because VirtualizingStackPanel does not destroy items when they are runs out of viewport.
I wrote simple app to reproduce this behavior.
Code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
for(int i = 0; i < 5000; ++i) // creating 5k text boxes
MyList.Items.Add(new TextBox() { Text = CurrText });
}
private void Button_Click(object sender, RoutedEventArgs e)
{
GC.Collect();
n = (n + 1) % 2; // switch 0 to 1 or 1 to 0
foreach (var item in MyList.Items)
((TextBox)item).Text = CurrText; // set new text
}
static int n = 0;
string CurrText { get { return new string(n.ToString()[0], 50); } }
}
XAML:
<Window x:Class="VPanel.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="700" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="5*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox Name="MyList" VirtualizingStackPanel.IsVirtualizing="True"/>
<Button Grid.Row="1" Content="UpdateText" Click="Button_Click"/>
</Grid>
</Window>
Clicking a button "UpdateText" updates all textboxes text. If slowly scroll to end by dragging scroller, "UpdateText" button clicks with a huge lag.
Don't create TextBoxes manually.
The word "virtualize" refers to a technique by which a subset of user interface (UI) elements are generated from a larger number of data items based on which items are visible on-screen. Generating many UI elements when only a few elements might be on the screen can adversely affect the performance of your application.
You create your UI items manually so it's already too late for virtualization. Use bindings and it will create TextBox from ItemTemplate whenever it is required. It will also not refresh TextBox.Text value if it's not currently in the view. To do that change your MainWindow to create ObservableCollection instead of TextBoxes and operate on that:
public partial class MainWindow : Window
{
private readonly ObservableCollection<string> _textBoxes = new ObservableCollection<string>();
public ICollection<string> TextBoxes { get { return _textBoxes; } }
private int n = 0;
private string CurrText { get { return new string(n.ToString()[0], 50); } }
public MainWindow()
{
for (int i = 0; i < 5000; ++i) _textBoxes.Add(CurrText);
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
n = (n + 1) % 2; // switch 0 to 1 or 1 to 0
for (int i = 0; i < _textBoxes.Count; i++) _textBoxes[i] = CurrText;
}
}
and then change XAML to bind to TextBoxes list property
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="5*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding TextBoxes}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=., Mode=TwoWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Grid.Row="1" Content="UpdateText" Click="Button_Click"/>
</Grid>
I have a listbox that various items are added to. When a new item is added to the listbox, I need to scroll that item into view (basically scroll to the bottom).
I've tried the solution from How can I have a ListBox auto-scroll when a new item is added? and also from this blog post
However, neither solutions work because my listbox contains variable height items. If I hack my listbox items templates to have a fixed height instead, then it seems to work. Here is an example of one of my item templates:
<DataTemplate x:Key="StatusMessageTemplate">
<Grid Grid.Column="1" VerticalAlignment="top" Margin="0,5,10,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Path=MessageText}" HorizontalAlignment="Left" Grid.Row="0" Grid.Column="0" FontWeight="Bold" Foreground="{DynamicResource LightTextColorBrush}"/>
<TextBlock Text="{Binding Path=created_at, StringFormat=t}" Style="{StaticResource Timestamp}" TextWrapping="Wrap" HorizontalAlignment="Right" Grid.Row="0" Grid.Column="1"/>
</Grid>
</DataTemplate>
How can I make the new items scroll into view regardless of their height?
I need to scroll that item into view (basically scroll to the bottom).
ScrollIntoView behaves strange when the Listbox has variable height items.
If the sole purpose is to scroll to the bottom, you can access the Scrollviewer directly and scroll to the maximum possible offset as shown below.
var scrollViewer = GetDescendantByType(ListBoxChats, typeof(ScrollViewer)) as ScrollViewer;
scrollViewer.ScrollToVerticalOffset(Double.MaxValue);
public static Visual GetDescendantByType(Visual element, Type type)
{
if (element == null)
{
return null;
}
if (element.GetType() == type)
{
return element;
}
Visual foundElement = null;
if (element is FrameworkElement)
{
(element as FrameworkElement).ApplyTemplate();
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
foundElement = GetDescendantByType(visual, type);
if (foundElement != null)
{
break;
}
}
return foundElement;
}
GetDescendantByType is an helper function written by punker76 # another SO post
I think I have found the problem. Variable height items are not calculated until displayed. So I add a timer to call the ScrollIntoView function. But even that didn't work well, so I used the VisualTreeHelper to find the ScrollViewer object and force it to the specific row. Here is the code.
System.Windows.Threading.DispatcherTimer dTimer = new System.Windows.Threading.DispatcherTimer();
dTimer.Interval = new TimeSpan(0, 0, 0, 0, 200); // 200 Milliseconds
dTimer.Tick += new EventHandler(
(seder, ea) =>
{
//Verses.ScrollIntoView(Verses.Items[itemIndex]);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(Verses); i++)
{
DependencyObject depObj = VisualTreeHelper.GetChild(Verses, i);
if (depObj is ScrollViewer)
{
ScrollViewer sv = depObj as ScrollViewer;
sv.ScrollToVerticalOffset(itemIndex); // Zero based index
break;
}
}
dTimer.Stop();
});
dTimer.Start();
I have a grid. I have to define each column and row manually, like this:
<Window x:Class="GridBuild"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GridBuild" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
</Grid>
I want to define the number of rows and columns with a single line, something like this:
<Window x:Class="GridBuild"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GridBuild" Height="300" Width="300">
<Grid>
<Grid.NumberOfRows="2"/>
<Grid.NumberOfColumns/>
</Grid>
</Window>
What you describe is called UniformGrid. It has Columns and Rows properties by which you can set the number of rows or columns that you want.
If you don't set these properties, the UniformGrid will try to layout the children as close to a square as it can. In this situation, it prefers to increase the number of columns before increasing the number of rows.
It's an obscure panel, but it's extremely powerful when used correctly.
I propose deriving from Grid and adding these properties to it like following:
public class GridEx : Grid
{
public int NumberOfRows
{
get { return RowDefinitions.Count; }
set
{
RowDefinitions.Clear();
for (int i = 0; i < value; i++)
RowDefinitions.Add(new RowDefinition());
}
}
public int NumberOfColumns
{
get { return ColumnDefinitions.Count; }
set
{
ColumnDefinitions.Clear();
for (int i = 0; i < value; i++)
ColumnDefinitions.Add(new ColumnDefinition());
}
}
}
Now it can be used as following:
<local:GridEx NumberOfRows="3" NumberOfColumns="2">
<TextBox>some text</TextBox>
<TextBox Grid.Row="1">some text</TextBox>
<TextBox Grid.Row="2">some text</TextBox>
<TextBox Grid.Column="1">some text</TextBox>
<TextBox Grid.Row="1" Grid.Column="1">some text</TextBox>
<TextBox Grid.Row="2" Grid.Column="1">some text</TextBox>
</local:GridEx>
Works in designer as well by the way :)
The challenge here is to set different Width, Height etc. for different rows and columns. I have a nice thought how to do this. Also it is possible to automate assigning Grid.Row and Grid.Column. Think of it :)
The above answer by EvAlex will work, but only if you don't want to set the number of column/rows using data binding.
public class GridEx : Grid
{
public int NumberOfRows
{
get { return RowDefinitions.Count; }
set
{
RowDefinitions.Clear();
for (int i = 0; i < value; i++)
RowDefinitions.Add(new RowDefinition());
}
}
public int NumberOfColumns
{
get { return ColumnDefinitions.Count; }
set
{
ColumnDefinitions.Clear();
for (int i = 0; i < value; i++)
ColumnDefinitions.Add(new ColumnDefinition());
}
}
}
If you do want to set these via data binding (like I do), then with the above solution the compiler will complain because it needs DependencyProperties for that. A DependencyProperty can be implemented (using C# 6's nameof operator) as follows (a quick way to insert it is using the snippet propdp):
public int Columns
{
get { return (int) GetValue(ColumnsDependencyProperty); }
set { SetValue(ColumnsDependencyProperty, value); }
}
public static readonly DependencyProperty ColumnsDependencyProperty =
DependencyProperty.Register(nameof(Columns), typeof(int), typeof(GridEx), new PropertyMetadata(0));
But this way you can't execute the necessary logic to add the necessary number of RowDefinitions. To solve this, define a DependencyPropertyDescriptor for each DependencyProperty and add an AddValueChanged call with the necessary logic to it in your custom class's constructor. The result per propery is then (using C# 6's null-conditional operator ?.):
public int Columns
{
get { return (int) GetValue(ColumnsDependencyProperty); }
set { SetValue(ColumnsDependencyProperty, value); }
}
public static readonly DependencyProperty ColumnsDependencyProperty =
DependencyProperty.Register(nameof(Columns), typeof(int), typeof(GridEx), new PropertyMetadata(0));
DependencyPropertyDescriptor ColumnsPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ColumnsDependencyProperty, typeof(GridEx));
public GridEx()
{
ColumnsPropertyDescriptor?.AddValueChanged(this, delegate
{
ColumnDefinitions.Clear();
for (int i = 0; i < Columns; i++)
ColumnDefinitions.Add(new ColumnDefinition());
});
}
Look here for a helper that does exactly what You ask:
https://rachel53461.wordpress.com/2011/09/17/wpf-grids-rowcolumn-count-properties/
If You have added the helper from the above link to Your project, You can use it like that:
<Grid local:GridHelpers.RowCount="6"
local:GridHelpers.StarRows="5"
local:GridHelpers.ColumnCount="4"
local:GridHelpers.StarColumns="1,3">
</Grid>