I got a problem with the GridSplitter
Simple example code:
<Grid x:Name="MainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="100"></RowDefinition>
<RowDefinition Height="2"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid x:Name="TopGrid" Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Red"/>
<GridSplitter ResizeDirection="Rows" Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Height="2" Background="Black" />
<Grid x:Name="BottomGrid" Grid.Row="2" HorizontalAlignment="Stretch" Background="Aquamarine" VerticalAlignment="Stretch"/>
</Grid>
This creates two Grids vertically seperated by a GridSplitter.
What I want to achieve is, that the GridSplitter automaticly align to the Grid´s content.
For example if I got a collapsable element in the bottom Grid, I want the top Grid to become bigger if I collapse the element. If I expand it, the top Grid should become smaller.
How do I do this? Later I will have 4 Grids with 3 GridSplitter´s... so the solution should work with multiple GridSplitter´s as well.
[edit]:
my xaml:
<Grid x:Name="MainGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="100">
<RowDefinition.MinHeight>
<MultiBinding Converter="{StaticResource ResourceKey=MultiValueConverter}">
<Binding ElementName="dgStapelliste" Path="ActualHeight"></Binding>
</MultiBinding>
</RowDefinition.MinHeight>
</RowDefinition>
<RowDefinition Height="1"></RowDefinition>
<RowDefinition Height="*"></RowDefinition >
</Grid.RowDefinitions>
<Grid Grid.Row="0" Grid.Column="0" x:Name="Test">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<WPF:DataGrid GridHeaderContent="{Binding StapelListe.HeaderText}" SelectedItem="{Binding StapelListe.SelectedItem}" Grid.Row="0" Grid.Column="0" x:Name="dgStapelliste" HorizontalAlignment="Stretch" ItemsSource="{Binding StapelListe, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<WPF:DataGrid GridHeaderContent="{Binding EinzelListe.HeaderText}" Grid.Row="0" Grid.Column="1" x:Name="dgEinzelliste" HorizontalAlignment="Stretch" ItemsSource="{Binding EinzelListe, Mode=OneWay}"/>
<GridSplitter Grid.Column="0" Width="1" VerticalAlignment="Stretch" Background="Black" />
</Grid>
<Grid Grid.Row="2" Grid.Column="0" Grid.RowSpan="2">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<WPF:DataGrid GridHeaderContent="{Binding Anforderungsliste.HeaderText}" Grid.Column="0" x:Name="dgAnforderungsliste" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding Anforderungsliste, Mode=OneWay}"/>
<GridSplitter Grid.Column="0" Width="1" VerticalAlignment="Stretch" Background="Black" />
</Grid>
<GridSplitter Grid.Column="0" Grid.Row="1" Height="1" ResizeDirection="Rows" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="Black" x:Name="testSplitter" />
</Grid>
You could use MultiBinding to achieve what you want.
For each RowDefinition you set a MinHeight which is bound to its contents ActualHeight's like so:
<RowDefinition Height="100">
<RowDefinition.MinHeight>
<MultiBinding Converter="{StaticResource ResourceKey=CalcAll}">
<Binding ElementName="firstElementInThisRow" Path="ActualHeight"></Binding>
<Binding ElementName="secondElementInThisRow" Path="ActualHeight"></Binding>
<Binding ElementName="thirdElementInThisRow" Path="ActualHeight"></Binding>
<Binding ElementName="fourthElementInThisRow" Path="ActualHeight"></Binding>
</MultiBinding>
</RowDefinition.MinHeight>
</RowDefinition>
Your Converter could look like:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double result = 0.0;
foreach (object item in values)
{
result += System.Convert.ToDouble(item);
}
return result;
}
public object[] ConvertBack(object values, Type[] targetType, object parameter, CultureInfo culture)
{
return null;
}
Everytime you expand a control, its ActualHeight changes and the Binding gets update -> MinHeight of the parents RowDefinition changes.
But you cant set one if the controls VerticalAlignment to Stretch, because then the ActualHeight wouldn't change by expanding.
EDIT: Since the only property I can now think of is the DesiredSize.Height-property, you can't use Binding (the binding won't update, if the DesiredSize.Height-value changes).
But perhaps you can use a property (let's call it MinHeightRowOne) of type double which raises the PropertyChanged event in it's setter and is bound to the first rows MinHeight (one property for each row):
public double _minHeightRowOne;
public double MinHeightRowOne
{
get
{
return _minHeightRowOne;
}
set
{
_minHeightRowOne = value;
OnPropertyChanged("MinHeightRowOne");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
<RowDefinition Height="100" MinHeight="{Binding Path=MinHeightRowOne}"/>
Now add this EventHandler to the SizeChanged-Event of every control in the first row (one handler for each row):
private List<KeyValuePair<string,double>> oldVals = new List<KeyValuePair<string,double>>();
private void ElementInRowOneSizeChanged(object sender, SizeChangedEventArgs e)
{
FrameworkElement elem = (FrameworkElement)sender;
MinHeightRowOne -= oldVals.Find(kvp => kvp.Key == elem.Name).Value;
elem.Measure(new Size(int.MaxValue, int.MaxValue));
MinHeightRowOne += elem.DesiredSize.Height;
oldVals.Remove(oldVals.Find(kvp => kvp.Key == elem.Name));
oldVals.Add(new KeyValuePair<string, double>(elem.Name, elem.DesiredSize.Height));
}
Through this the MinHeight of the Rows get updated everytime the Size of a control changes (which should include expanding or collapsing items).
Note that every control must have an unique name to make this work.
Related
I have a wpf window with a grid. I would like to hide rows and I do with this code as shown in other stackoverflow threads:
buttonsGrid.Visibility = Visibility.Collapsed; or buttonsGrid.Height = 0;
dayTimePicker.Visibility = Visibility.Collapsed; or dayTimePicker.Height = 0;
Set Visibility to Collapsed or Height to 0 gives the same effect, but I would like the white space occupied by these Rows to sweep ... I would like the whole window to be occupied by the indexed row 0. .. How can I do it?
This is my window:
<Window x:Class="PerformanceVideoRec.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
xmlns:DTControl="clr-namespace:DayTimePickControl"
Title="{StaticResource ID_TITLE}" Height="600" Width="900" Loaded="Window_Loaded"> <!--Closing="Window_Closing"> -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="1" x:Name="ControlPart" IsEnabled="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="6*"/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" x:Name="LeftGrid">
<Grid.RowDefinitions>
<RowDefinition Height="55*"/>
<RowDefinition Height="5*"/>
<RowDefinition Height="5*"/>
<RowDefinition Height="20*"/>
</Grid.RowDefinitions>
<WindowsFormsHost Grid.Row="0" Margin="5" Background="Gray">
<wf:PictureBox x:Name="playWindow"></wf:PictureBox>
</WindowsFormsHost>
<UniformGrid Grid.Row="1">
<Button x:Name="btnShowHideControls" Content="Nascondi Controlli" Click="btnShowHideControls_Click"/>
</UniformGrid>
<UniformGrid Columns="6" Grid.Row="2" x:Name="buttonsGrid">
<Button x:Name="btnPause" Margin="5" Width="50" Content="{StaticResource ID_PAUSE}" Click="btnPause_Click"></Button>
<Button x:Name="btnPlay" Margin="5" Width="50" Content="{StaticResource ID_PLAY}" Click="btnPlay_Click" Visibility="Collapsed"/>
<Button x:Name="btnStop" Margin="5" Width="50" Content="{StaticResource ID_STOP}" Click="btnStop_Click"></Button>
<Button x:Name="btnSlow" Margin="5" Width="50" Content="{StaticResource ID_SLOW}" Click="btnSlow_Click"></Button>
<TextBox x:Name="tbSpeed" IsEnabled="False" Width="50" Height="25" TextAlignment="Center" VerticalContentAlignment="Center" Text="1X" />
<Button x:Name="btnFast" Margin="5" Width="50" Content="{StaticResource ID_FAST}" Click="btnFast_Click"></Button>
<Button x:Name="btnNormal" Margin="5" Width="50" Content="{StaticResource ID_NORMAL}" Click="btnNormal_Click"></Button>
</UniformGrid>
<DTControl:DayTimePick x:Name="dayTimePicker" Grid.Row="3" Width="550" Height="100" Grid.RowSpan="2" OnTimeClick="dayTimePicker_OnTimeClick"></DTControl:DayTimePick>
</Grid>
<Frame x:Name="PlayBackFrame" Grid.Column="1" Background="AliceBlue" ></Frame>
</Grid>
</Grid>
The MVVM way:
make a separate view model for the part of UI you want to collapse. Let's call it PlayViewModel,
make a DataTemplate for it,
expose it as a property (e.g. let's call it Play, don't forget to raise property change event) in the view model which is a DataContext for the whole view
you display it in your grid with ContentPresenter, ContentPresenter.Content bound to Play property
you do hiding by null-ing Play property, you show it by restoring it.
You get testability for free.
You could use Data Binding and bind the the property Visibility of the control inside the row you want to collapse with a converter.
I usually use the NuGet package Mvvm Light, documentation to have some facilities while using Data Binding. (In this case RelayCommand, RaisePropertyChanged implementation)
A very basic example:
Part of the view. Here you can notice the Binding between the Visibility property of the button and the property IsButton1Visible of the ViewModel. Of course bool and Visibility aren't the same type, so I had to create a mapping using an IValueConverter:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="1" Visibility="{Binding IsButton1Visible, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Button Grid.Row="1" Content="2" />
<Button Grid.Row="2" Content="3" />
<Button Grid.Row="3" Content="4" />
<Button Grid.Row="4" Content="Toggle button 1 visibility" Command="{Binding ToggleButton1Visibility}" />
</Grid>
The window's constructor in the code behind (you could also bind the ViewModel via the DataContext property directly into the View), used to associate the View with the ViewModel:
public MainWindow()
{
InitializeComponent();
this.DataContext = new MyWindowViewModel();
}
The converter to convert true to Visibility.Visible:
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> (bool)value ? Visibility.Visible : Visibility.Collapsed;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
App.xaml, Application.Resource part, used to make the View "see" the converter:
<Application.Resources>
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Application.Resources>
The ViewModel. Here RaisePropertyChanged of set method of IsButton1Visible is very important because it notifies the View that the property is changed, so that the View can refresh itself:
public class MyWindowViewModel : ViewModelBase
{
private bool _isButton1Visibile = true;
public bool IsButton1Visible
{
get => _isButton1Visibile;
set
{
if (_isButton1Visibile == value)
return;
_isButton1Visibile = value;
RaisePropertyChanged(nameof(IsButton1Visible));
}
}
RelayCommand _toggleButton1Visbility;
public RelayCommand ToggleButton1Visibility
{
get
{
return _toggleButton1Visbility ?? (_toggleButton1Visbility = new RelayCommand(
() =>
{
IsButton1Visible = !IsButton1Visible;
}));
}
}
}
LeftGrid.RowDefinitions[2].Height = new GridLength(0);
LeftGrid.RowDefinitions[3].Height = new GridLength(0);
If you want to restore make this
LeftGrid.RowDefinitions[2].Height = new GridLength(5, GridUnitType.Star);
LeftGrid.RowDefinitions[3].Height = new GridLength(20, GridUnitType.Star);
I have following WPF XAML file,
<Window x:Class="Program"
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:Program"
mc:Ignorable="d"
Title="Print Preview" Height="40820.962" Width="2135.146">
<Grid Margin="10,10,2,-21" Height="40801" VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="131*"/>
<RowDefinition Height="40670*"/>
</Grid.RowDefinitions>
<Grid HorizontalAlignment="Left" Height="3438" Margin="20,126,0,0" VerticalAlignment="Top" Width="2095" Grid.RowSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="500*"/>
<ColumnDefinition Width="1072*"/>
<ColumnDefinition Width="523*"/>
</Grid.ColumnDefinitions>
<Label x:Name="label5" Content="Here" Grid.Column="1" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" RenderTransformOrigin="-7.455,-0.374" Height="58" Width="171" FontSize="16"/>
</Grid>
<Grid HorizontalAlignment="Right" Height="432" Margin="0,3453,1605,0" Grid.Row="1" VerticalAlignment="Top" Width="490" RenderTransformOrigin="0.62,1.205">
<Grid.RowDefinitions>
<RowDefinition Height="143*"/>
<RowDefinition Height="136*"/>
<RowDefinition Height="153*"/>
</Grid.RowDefinitions>
</Grid>
<Grid HorizontalAlignment="Right" Height="452" Margin="0,3433,1605,0" Grid.Row="1" VerticalAlignment="Top" Width="490" RenderTransformOrigin="0.62,1.205">
<Grid.RowDefinitions>
<RowDefinition Height="143*"/>
<RowDefinition Height="136*"/>
<RowDefinition Height="153*"/>
</Grid.RowDefinitions>
</Grid>
<Grid HorizontalAlignment="Left" Height="447" Margin="1594,3438,0,0" Grid.Row="1" VerticalAlignment="Top" Width="511">
<Grid.RowDefinitions>
<RowDefinition Height="142*"/>
<RowDefinition Height="156*"/>
<RowDefinition Height="149*"/>
</Grid.RowDefinitions>
</Grid>
<Grid HorizontalAlignment="Left" Height="452" Margin="510,3433,0,0" Grid.Row="1" VerticalAlignment="Top" Width="1084">
<Grid.RowDefinitions>
<RowDefinition Height="44*"/>
<RowDefinition Height="45*"/>
</Grid.RowDefinitions>
</Grid>
<Grid HorizontalAlignment="Left" Height="23141" Margin="20,3895,0,0" Grid.Row="1" VerticalAlignment="Top" Width="1574"/>
<Grid HorizontalAlignment="Left" Height="23540" Margin="1599,3496,0,0" Grid.Row="1" VerticalAlignment="Top" Width="506">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="14.143"/>
<ColumnDefinition Width="146.857"/>
<ColumnDefinition Width="42.714"/>
<ColumnDefinition Width="119*"/>
<ColumnDefinition Width="98*"/>
<ColumnDefinition Width="85*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="87*"/>
<RowDefinition Height="23516*"/>
</Grid.RowDefinitions>
<Label x:Name="label" Content="Hespanic" HorizontalAlignment="Left" Margin="-1,-61,0,0" VerticalAlignment="Top" Height="55" Width="506" FontSize="22" Grid.ColumnSpan="6"/>
</Grid>
<Label x:Name="label1" Content="Sample" HorizontalAlignment="Left" Margin="8,97,0,0" VerticalAlignment="Top" RenderTransformOrigin="-8.5,0.654" Width="215"/>
<Label x:Name="label2" Content="Layer" HorizontalAlignment="Left" Height="33" Margin="922,10,0,0" VerticalAlignment="Top" Width="232" FontSize="18"/>
<Label x:Name="label3" Content="Index" HorizontalAlignment="Left" Margin="1969,10,0,0" VerticalAlignment="Top" Width="105"/>
<Label x:Name="label4" Content="People" HorizontalAlignment="Left" Margin="1477,84,0,0" VerticalAlignment="Top" Width="161"/>
</Grid>
</Window>
So I'm trying to add thickness = 1, outline for grid borders, rows and columns
So I tried following thread
How do i put a border on my grid in WPF?
So to add a border I added following thing, and its working fine
<Border BorderBrush="Black" BorderThickness="2">
<Grid>
<!-- Grid contents here -->
</Grid>
</Border>
But since I have need to add thickness = 1, outline for all above multiple columns and rows also, I tried something like this
</Grid.ColumnDefinitions>
<Border Grid.Row="0" Grid.Column="0" BorderThickness="1" BorderBrush="Black"/>
<Border Grid.Row="0" Grid.Column="1" BorderThickness="1" BorderBrush="Black"/>
which is identifying each column and row and add thickness to them, but this seems quite time consuming and confusing work.
Is there any other proper and quick way to add BorderThickness="1" BorderBrush="Black" to all above Columns and Rows in the grids ?
In the default WPF Grid, you can set ShowGridLines="True". However these lines are meant to be designer lines, and not meant for end use.
The common solution I use is a custom GridControl which adds DependencyProperties for GridLines settings, and overrides OnRender to draw them.
public class GridControl : Grid
{
#region Properties
public bool ShowCustomGridLines
{
get { return (bool)GetValue(ShowCustomGridLinesProperty); }
set { SetValue(ShowCustomGridLinesProperty, value); }
}
public static readonly DependencyProperty ShowCustomGridLinesProperty =
DependencyProperty.Register("ShowCustomGridLines", typeof(bool), typeof(GridControl), new UIPropertyMetadata(false));
public Brush GridLineBrush
{
get { return (Brush)GetValue(GridLineBrushProperty); }
set { SetValue(GridLineBrushProperty, value); }
}
public static readonly DependencyProperty GridLineBrushProperty =
DependencyProperty.Register("GridLineBrush", typeof(Brush), typeof(GridControl), new UIPropertyMetadata(Brushes.Black));
public double GridLineThickness
{
get { return (double)GetValue(GridLineThicknessProperty); }
set { SetValue(GridLineThicknessProperty, value); }
}
public static readonly DependencyProperty GridLineThicknessProperty =
DependencyProperty.Register("GridLineThickness", typeof(double), typeof(GridControl), new UIPropertyMetadata(1.0));
#endregion
protected override void OnRender(DrawingContext dc)
{
if (ShowCustomGridLines)
{
foreach (var rowDefinition in RowDefinitions)
{
dc.DrawLine(new Pen(GridLineBrush, GridLineThickness), new Point(0, rowDefinition.Offset), new Point(ActualWidth, rowDefinition.Offset));
}
foreach (var columnDefinition in ColumnDefinitions)
{
dc.DrawLine(new Pen(GridLineBrush, GridLineThickness), new Point(columnDefinition.Offset, 0), new Point(columnDefinition.Offset, ActualHeight));
}
dc.DrawRectangle(Brushes.Transparent, new Pen(GridLineBrush, GridLineThickness), new Rect(0, 0, ActualWidth, ActualHeight));
}
base.OnRender(dc);
}
static GridControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(GridControl), new FrameworkPropertyMetadata(typeof(GridControl)));
}
}
It can be used like this :
<local:GridEx ShowCustomGridLines="True"
GridLineBrush="#FF38B800"
GridLineThickness="2">
...
</local:GridEx>
In my WPF Application I have a two textblocks which get filled from the code behind with a run. Every second line should have a different background color so it gets easier to read. Unfortunately, the lines are only dyed as far as they are written. But I want the background color to go over the entire line and not just the written area.
Code:
for (int i = 0; i < GlobalSettings.prefixList.Count; i++)
{
runLeft = new Run(GlobalSettings.prefixList[i].prefix + "\n");
runRight = new Run(GlobalSettings.prefixList[i].amount + "\n");
if (i % 2 == 0)
{
runLeft.Background = Brushes.Gray;
runRight.Background = Brushes.Gray;
}
else
{
runLeft.Background = Brushes.LightGray;
runRight.Background = Brushes.LightGray;
}
tblock_StatisticsLeft.Inlines.Add(runLeft);
tblock_StatisticsRight.Inlines.Add(runRight);
}
Example Picture
The two textblocks are seamlessly together in the middle so it would look like it is a single line in a single textblock. The spaces between the lines are negligible if this makes it easier.
Is there a solution without using a textbox or richtextbox?
EDIT:
XAML Code:
<UserControl x:Class="MuseKeyGenApp.UCStartUp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MuseKeyGenApp"
mc:Ignorable="d"
Background = "#FF0069B4"
d:DesignHeight="500" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="26"/>
<RowDefinition Height="0"/>
<RowDefinition Height="*" MinHeight="80"/>
<RowDefinition Height="*" MinHeight="80"/>
<RowDefinition Height="*" MinHeight="80"/>
<RowDefinition Height="*" MinHeight="80"/>
<RowDefinition Height="*" MinHeight="80"/>
<RowDefinition Height="0"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" MinWidth="150"/>
<ColumnDefinition Width="0.75*" MinWidth="100"/>
<ColumnDefinition Width="0.75*" MinWidth="100"/>
<ColumnDefinition Width="0.75*" MinWidth="100"/>
<ColumnDefinition Width="0.75*" MinWidth="100"/>
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Row="2" Grid.RowSpan="3" Grid.Column="1" Grid.ColumnSpan="2" Margin="10,35,12,12" Name="sv_PrefixList">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" x:Name="tblock_StatisticsLeft" HorizontalAlignment="Stretch" LineHeight="20" TextWrapping="Wrap" Margin="0,0,0,0" Text="TextBlock" VerticalAlignment="Stretch" TextAlignment="Left"/>
<TextBlock Grid.Column="1" x:Name="tblock_StatisticsRight" HorizontalAlignment="Stretch" LineHeight="20" TextWrapping="Wrap" Margin="0,0,10,0" Text="TextBlock" VerticalAlignment="Stretch" TextAlignment="Right"/>
</Grid>
</ScrollViewer>
</Grid>
I left out all the other stuff of the control that is not relevant.
You could (should) use an ItemsControl. Set the ItemsSource property of it to your GlobalSettings.prefixList collection, i.e. you replace your for loop with this:
ic.ItemsSource = GlobalSettings.prefixList;
Make sure that "prefix" and "amount" are public properties (and not fields) of your "prefix" type or whatever you call it:
public string prefix { get; set; }
You then put the Grid with the TextBlocks in the ItemTemplate of the ItemsControl and bind the TextBlocks to the "prefix" and "amount" properties:
<ScrollViewer Grid.Row="2" Grid.RowSpan="3" Grid.Column="1" Grid.ColumnSpan="2" Margin="10,35,12,12" Name="sv_PrefixList">
<ItemsControl x:Name="ic" AlternationCount="2">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="grid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" x:Name="tblock_StatisticsLeft" HorizontalAlignment="Stretch" LineHeight="20" TextWrapping="Wrap" Margin="0,0,0,0" VerticalAlignment="Stretch" TextAlignment="Left"
Text="{Binding prefix}"/>
<TextBlock Grid.Column="1" x:Name="tblock_StatisticsRight" HorizontalAlignment="Stretch" LineHeight="20" TextWrapping="Wrap" Margin="0,0,10,0" VerticalAlignment="Stretch" TextAlignment="Right"
Text="{Binding amount}"/>
</Grid>
<DataTemplate.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="Gray" TargetName="grid"/>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="LightGray" TargetName="grid"/>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
This should get you coloured lines.
Here is the correct way of doing this:
Xaml:
<Window.Resources>
<local:BackConverter x:Key="BackConverter"/>
</Window.Resources>
<Grid Margin="10">
<ItemsControl Name="ic">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DockPanel LastChildFill="False">
<DockPanel.Background>
<MultiBinding Converter="{StaticResource BackConverter}">
<Binding />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ItemsControl}"/>
</MultiBinding>
</DockPanel.Background>
<TextBlock DockPanel.Dock="Left" Text="{Binding Left}">
</TextBlock>
<TextBlock DockPanel.Dock="Right" Text="{Binding Right}">
</TextBlock>
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
c# code:
public class obj
{
public string Left { get; set; }
public string Right { get; set; }
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
List<obj> objects = new List<obj>();
for (int i = 0; i < 10; i++)
{
var left = "aaaaa";
var right = "bbbbb";
objects.Add(new obj() { Left = left, Right = right });
}
ic.ItemsSource = objects;
}
the converter:
public class BackConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var index = ((ItemsControl)values[1]).Items.IndexOf(values[0]);
if (index % 2 == 0)
return Brushes.Gray;
return Brushes.White;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I am trying to add user controls to the Grid and I am trying to set the Grid.Column and Grid.Row property in the DataTemplate but it does not have any effect on the rendering. Can someone help to point out what could be wrong in the code below.
I have the main window with the following code:
<ItemsControl ItemsSource="{Binding Controls}">
<ItemsControl.ItemsPanel >
<ItemsPanelTemplate>
<Grid Name="MainGrid">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<AppControls:TagInfo Grid.Row="{Binding RowIndex}"
Grid.Column="{Binding ColumnIndex}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
TagInfo.xaml.cs
public partial class TagInfo : UserControl
{
public TagInfo()
{
InitializeComponent();
}
public int RowIndex { get; set; }
public int ColumnIndex { get; set; }
}
TagInfo.xaml
<UserControl x:Class="DataSimulator.WPF.Controls.TagInfo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Height="258" Width="302"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Grid Margin="2,2,2,2">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Tag"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding TagInfo.TagName}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="High High"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding TagInfo.HighHigh}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="High"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding TagInfo.High}"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Low Low"/>
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding TagInfo.LowLow}"/>
<TextBlock Grid.Row="4" Grid.Column="0" Text="Low"/>
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding TagInfo.Low}"/>
<TextBlock Grid.Row="5" Grid.Column="0" Text="Range Start"/>
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding TagInfo.RangeStart}"/>
<TextBlock Grid.Row="6" Grid.Column="0" Text="Range End"/>
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding TagInfo.RangeEnd}"/>
<Button Grid.Row="7" Grid.Column="0" Grid.ColumnSpan="2" Content="Save"/>
</Grid>
</UserControl>
ViewModel
private ObservableCollection<Controls.TagInfo> _controls
= new ObservableCollection<Controls.TagInfo>();
public ObservableCollection<Controls.TagInfo> Controls
{
get { return _controls; }
set { _controls = value; }
}
private void AddControl()
{
if(currentRow == 3)
{
currentRow = 0;
}
if(currentColumn == 3)
{
currentColumn = 0;
currentRow++;
}
var tagInfoUserControl = new Controls.TagInfo();
tagInfoUserControl.RowIndex = currentRow;
tagInfoUserControl.ColumnIndex = currentColumn++;
_controls.Add(tagInfoUserControl);
}
I am trying to add user controls to the grid and I am trying to set the Grid.Column and Grid.Row property in the data template but it does not have any effect on the rendering
We can't do the attached property binding in a DataTemplate, creating a style for your UserControl in ItemsControl.ItemContainerStyle can make it work:
<ItemsControl ItemsSource="{Binding Controls}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid Name="MainGrid">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="AppControls:TagInfo">
<Setter Property="Grid.Row" Value="{Binding RowIndex}"/>
<Setter Property="Grid.Column" Value="{Binding ColumnIndex}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Screenshot:
when all columns and rows have to be of equal size and elements are consequtive, it is probably simpler to use UniformGrid. Rows and Columns properties support binding.
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Name="MainGrid" Rows="4" Columns="4"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
here is a nice example of Grid ItemsPanel here: WPF Grid as ItemsPanel for a list dynamically bound to an ItemsControl
I have two grids with three rows each. The first and last row of each grid has a fixed height while the middle rows have auto height and share their height using SharedSizeGroup.
First, the content of the right grid determines the height of the size group. When the content of the right grid shrinks so that the content of the left grid determines the height of the size group, the overall size of the left grid is not adjusted correctly.
See the following sample app. Click the increase button to increase the size of the textblock in the right grid. The size of the left grid changes accordingly. Now decrease the size. When the textblock becomes smaller than the textblock in the left grid, the total size of the left grid doesn't shrink. Is this a bug or am i missing something?
<StackPanel Orientation="Horizontal" Grid.IsSharedSizeScope="True">
<StackPanel Orientation="Vertical" Width="100">
<Grid Background="Green">
<Grid.RowDefinitions>
<RowDefinition Height="15" />
<RowDefinition SharedSizeGroup="Group1" />
<RowDefinition Height="15" />
</Grid.RowDefinitions>
<TextBlock Background="Blue" Grid.Row="0" />
<TextBlock Background="Red" Grid.Row="1" Name="textBlock1" Height="100" />
<TextBlock Background="Blue" Grid.Row="2" />
</Grid>
<TextBlock Text="{Binding Path=ActualHeight, ElementName=grid1}" />
</StackPanel>
<StackPanel Orientation="Vertical" Width="100">
<Grid Background="Green">
<Grid.RowDefinitions>
<RowDefinition Height="15" />
<RowDefinition SharedSizeGroup="Group1" />
<RowDefinition Height="15" />
</Grid.RowDefinitions>
<TextBlock Background="Blue" Grid.Row="0" />
<TextBlock Background="Red" Grid.Row="1" Name="textBlock2" Height="150" />
<TextBlock Background="Blue" Grid.Row="2" />
</Grid>
<TextBlock Text="{Binding Path=ActualHeight, ElementName=grid2}" />
</StackPanel>
<Button Height="24" Click="Button_Click_1" VerticalAlignment="Top">Increase</Button>
<Button Height="24" Click="Button_Click_2" VerticalAlignment="Top">Decrease</Button>
</StackPanel>
private void Button_Click_1(object sender, RoutedEventArgs e)
{
textBlock2.Height += 30;
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
textBlock2.Height -= 30;
}
The rows are staying the same height - the size of the second TextBlock is changing, while the size of the first TextBlock remains 100.
To demonstrate this, make the following changes:
Add ShowGridLines="True" to each of your Grids
Change your named TextBlocks to show their ActualHeight as their text:
<TextBlock Background="Red" Grid.Row="1" Name="textBlock2" Height="150"
Text="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"/>
You will see that the SharedSizeGroup row will be the maximum ActualHeight of the two TextBlocks.
Update: A Short Project To Show What's Happening
I've created a quick-n-dirty project to show what's happening. It turns out that when the right grid gets smaller than the original size, the left grid does an Arrange but not a Measure. I am not sure I understand this completely - I have posted a follow-up question with this same solution.
Here is the solution that I created, based on your original. It simply wraps the basic grid in a custom class that spews out info (via event) when Measure and Arrange are called. In the main window, I just put that info into a list box.
InfoGrid and InfoGridEventArgs classes
using System.Windows;
using System.Windows.Controls;
namespace GridMeasureExample
{
class InfoGrid : Grid
{
protected override Size ArrangeOverride(Size arrangeSize)
{
CallReportInfoEvent("Arrange");
return base.ArrangeOverride(arrangeSize);
}
protected override Size MeasureOverride(Size constraint)
{
CallReportInfoEvent("Measure");
return base.MeasureOverride(constraint);
}
public event EventHandler<InfoGridEventArgs> ReportInfo;
private void CallReportInfoEvent(string message)
{
if (ReportInfo != null)
ReportInfo(this, new InfoGridEventArgs(message));
}
}
public class InfoGridEventArgs : EventArgs
{
private InfoGridEventArgs()
{
}
public InfoGridEventArgs(string message)
{
this.TimeStamp = DateTime.Now;
this.Message = message;
}
public DateTime TimeStamp
{
get;
private set;
}
public String Message
{
get;
private set;
}
}
}
Main Window XAML
<Window x:Class="GridMeasureExample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:GridMeasureExample"
Title="SharedSizeGroup" Height="500" Width="500">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Column="0"
Grid.Row="0"
Orientation="Horizontal"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Grid.IsSharedSizeScope="True">
<StackPanel Orientation="Vertical" Width="100">
<local:InfoGrid x:Name="grid1" Background="Green" ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="15" />
<RowDefinition SharedSizeGroup="Group1" />
<RowDefinition Height="15" />
</Grid.RowDefinitions>
<TextBlock Background="Blue" Grid.Row="0" Text="Row 0"/>
<TextBlock Background="Red" Grid.Row="1" Name="textBlock1" Height="100"
Text="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"/>
<TextBlock Background="Blue" Grid.Row="2" Text="Row 2" />
</local:InfoGrid>
<TextBlock Text="{Binding Path=ActualHeight, ElementName=grid1}" />
</StackPanel>
<StackPanel Orientation="Vertical" Width="100">
<local:InfoGrid x:Name="grid2" Background="Yellow" ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="15" />
<RowDefinition SharedSizeGroup="Group1" />
<RowDefinition Height="15" />
</Grid.RowDefinitions>
<TextBlock Background="Orange" Grid.Row="0" Text="Row 0" />
<TextBlock Background="Purple" Grid.Row="1" Name="textBlock2" Height="150"
Text="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"/>
<TextBlock Background="Orange" Grid.Row="2" Text="Row 2" />
</local:InfoGrid>
<TextBlock Text="{Binding Path=ActualHeight, ElementName=grid2}" />
</StackPanel>
</StackPanel>
<ListBox x:Name="lstInfo"
Grid.Column="1"
Grid.Row="0"
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
<UniformGrid Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="2"
Columns="2"
HorizontalAlignment="Center"
Margin="5">
<Button x:Name="btnIncrease" Margin="4,0">Increase</Button>
<Button x:Name="btnDecrease" Margin="4,0">Decrease</Button>
</UniformGrid>
</Grid>
</Window>
Main Window Constructor (only code in code-behind)
public Window1()
{
InitializeComponent();
btnIncrease.Click += (s, e) =>
{
lstInfo.Items.Add(String.Format("{0} Increase Button Pressed", DateTime.Now.ToString("HH:mm:ss.ffff")));
textBlock2.Height += 30;
};
btnDecrease.Click += (s, e) =>
{
lstInfo.Items.Add(String.Format("{0} Decrease Button Pressed", DateTime.Now.ToString("HH:mm:ss.ffff")));
if (textBlock2.ActualHeight >= 30)
textBlock2.Height -= 30;
};
grid1.ReportInfo += (s, e) => lstInfo.Items.Add(String.Format("{0} Left Grid: {1}", e.TimeStamp.ToString("HH:mm:ss.ffff"), e.Message));
grid2.ReportInfo += (s, e) => lstInfo.Items.Add(String.Format("{0} Right Grid: {1}", e.TimeStamp.ToString("HH:mm:ss.ffff"), e.Message));
}