Grid alternate color - c#

I am trying to display a Grid with alternating color each other row. The best would be alternating template all together to be able to change the whole row.
To make sure there is no misunderstanding when i mean grid i mean grid, not datagrid, not gridview, grid as in <Grid></Grid>
Right now the only viable solution i have found was to make a grid with the appropriate amount of rows i want and in each rows i put another grid with 1 row and the 3 columns i need and copy pasted for each row by changing the back color. As you can see this is not very clean solution.
So i have looked around and found that the listbox can have the alternate count and on a simple trigger it can change everything and that was perfect until i noticed the highlight CANNOT be disabled. You can change the highlight brush but it override an alternate color so both cannot be used at the same time. Before you asked yes i did use the transparent bush and transparent is NOT really transparent, All it does is that it shows the color underneath the ListBox control which was the beige color of the canvas underneath and the item itself disappear.
Anyone know a way to apply a template of alternating row on a grid. Putting a simple style on the RowDefinition would be easy but since you can't really tell the type of element you put in a grid i doubt there is something that can be done as easy as that.
EDIT:
here my latest change. a little bit "cleaner"
i created 2 style for item control for each color theme like
<Style x:Key="PropertyGrid" TargetType="ItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="2"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
</Grid>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="AlternatePropertyGrid" TargetType="ItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Grid Background="#FFEBEBEB">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="2"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
</Grid>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
Then i used a stack panel and listed each line
<StackPanel>
<ItemsControl Style="{StaticResource PropertyGrid}">
<Label Content="Identifier" Style="{StaticResource PropertyNameLabel}"/>
<Label Content="{Binding ElementName=cboActions, Path=SelectedItem.UniqueIdentifier}" Style="{StaticResource PropertyValueLabel}"/>
<Rectangle Style="{StaticResource PropertyGridSplitter}"/>
</ItemsControl>
<ItemsControl Style="{StaticResource AlternatePropertyGrid}">
<Label Content="Source" Style="{StaticResource AlternatePropertyNameLabel}"/>
<Label Name="lblSource" Style="{StaticResource AlternatePropertyValueLabel}"/>
<Rectangle Style="{StaticResource AlternatePropertyGridSplitter}"/>
</ItemsControl>
<ItemsControl Style="{StaticResource PropertyGrid}">
<Label Content="Description" Style="{StaticResource PropertyNameLabel}"/>
<Label Name="lblActionDescription" Style="{StaticResource PropertyValueLabel}"/>
<Rectangle Style="{StaticResource PropertyGridSplitter}"/>
</ItemsControl>
</StackPanel>
each item in their respective style they have the grid column preset so style determine the location in the grid of each object within the item control.
It works but still i feel like there is probably a more efficient way to do it. here's a screenshot of what it look like right now, might be more helpful for the visual people to know what i am trying to achieve here.

With a Datagrid, you can easily do that :
<DataGrid AlternatingRowBackground="Blue"/>
With a grid, no idea :p

For a listbox inside the Grid, we set alternate colors with a some lines of code (code-behind).
private void SetAlternateColor()
{
var blueBrush = new SolidColorBrush(Colors.Blue);
var redBrush = new SolidColorBrush(Colors.Red);
for (int i = 0; i < Items.Count; i++)
{
ListBoxItem item = TestListBox.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem;
item.Background = i % 2 == 0 ? blueBrush : redBrush;
}
}
private void TestGrid_Loaded(object sender, RoutedEventArgs e)
{
SetAlternateColor();
}
Don't know if this is what you are looking for.

Repeating the code was fastest solution.

Related

Xaml Responsive DataGrid with Scroll Bar

I have a grid with a Datagrid attached. I am having issues created a datagrid that resizes as the window resizes. I have set MinHeight and MinWidth, but it just seems to create a static Height and Width. I also have scroll bar set to be visible, but no scroll bar appears?
The closest I've come to responsive design is setting my Height="2300" and Width="2700", but the scroll bar still doesn't appear. And the table I plan to populate my Datagrid with will likely be much bigger than these dimensions, and need scrolling.
I have tried using techniques from other SE questions, but nothing seems to work how I'd expect.
<!--Grid View Assett Info - Populate Table from DB -->
<Grid x:Name="grid_AssetView" VerticalAlignment="Top" Margin="10,236,10,10" Style="{StaticResource Grid_Shadow}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid VerticalAlignment="Top"
Margin="0,0,0,0"
Grid.Column="0" Grid.Row="0"
ScrollViewer.CanContentScroll ="True"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
MinWidth="772" MinHeight="230"/>
</Grid>
My Style Sheets
<Style x:Key="Grid_Shadow" TargetType="Grid">
<Setter Property="Background" Value="#FFF9FBFD"/>
<Setter Property="BitmapEffect">
<Setter.Value>
<DropShadowBitmapEffect
Color="Black"
Direction="320"
ShadowDepth="10"
Softness="50"
Opacity="0.1">
</DropShadowBitmapEffect>
</Setter.Value>
</Setter>
</Style>
u have to set the ColumnDefinition Width="*" instead of "Auto" as follows
<!--Grid View Assett Info - Populate Table from DB -->
<Grid x:Name="grid_AssetView" VerticalAlignment="Top" Margin="10,236,10,10" Style="{StaticResource Grid_Shadow}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid VerticalAlignment="Top"
Margin="0,0,0,0"
Grid.Column="0" Grid.Row="0"
ScrollViewer.CanContentScroll ="True"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
MinHeight="230"/>
</Grid>
then the datagrid will resize automatically. I also removed MinWidth to show that resizing works without problems.
Also the scrollbars will be visible if there are contents.
I hope it helps.

WPF Binding TextBox width to grid column within a list box

I'm trying to build a simple (in terms of design) screen which will allow users to enter multiple rows of data.
The format of the screen is 4 columns (TextBox, TextBox, ComboBox, TextBox), with new rows added dynamically.
I have tried 2 approaches initially - one using a DataGrid, and one using a ListView.
The DataGrid caused many headaches with items retaining focus when they were clicked out of, and the ListView prevented me from being able to consistently access the underlying cell, instead returning the bound data object represented by the row.
My current approach uses a custom component which represents a row, and contains the 3 TextBox objects, and one ComboBox object.
One or more of these objects are displayed in a ListBox.
This approach allows for handling events more consistently, but is visually less straightforward to get working.
Currently, when a row is displayed, the first 3 columns in the grid (which have an explicitly defined width) display fine, but the textbox in the final column does not expand to fill the available width.
The relevant code for the panel:
<DockPanel LastChildFill="True" Name="basePanel">
<Grid x:Name="baseGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60" />
<ColumnDefinition Width="60"></ColumnDefinition>
<ColumnDefinition Width="55"></ColumnDefinition>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox Name="txtYear" Grid.Column="0" Style="{StaticResource txtComponentStyle}" Text="{Binding Path=RatingYear, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Name="txtArrears" Grid.Column="1" Style="{StaticResource txtComponentStyle}" Text="{Binding Path=ArrearsAmount, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox Name="cmbChangeCode" Grid.Column="2" ItemsSource="{Binding Path=ChangeCodes, Mode=OneTime}" SelectedItem="{Binding Path=ChangeCode}" />
<TextBox Name="txtComments" Grid.Column="3" Style="{StaticResource txtComponentStyle}" Text="{Binding Path=Comments, ValidatesOnDataErrors=True,UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" />
</Grid>
</DockPanel>
When I select a row in the ListBox, I can see that the object itself fills the entire width, yet the text box in the final column only expands to fit the content.
Setting the width in the column definition to * should (as I understand it) cause the column to expand to fille the available space, and setting the HorizontalAlignment property on the textbox to Stretch should cause the text box to fill the column.
The code below creates the ListBox.
<UserControl.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="0.5" Margin="1">
<DockPanel LastChildFill="True" HorizontalAlignment="Stretch" Background="DarkSlateBlue">
<Controls:ArrearsListEntry />
</DockPanel>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<DockPanel LastChildFill="True" Background="Red">
<ListBox IsSynchronizedWithCurrentItem="True" Name="lstRowData" ItemsSource="{Binding Path=Rows}" />
</DockPanel>
Is there a way to bind the width of the text box to the actual width of the column, or a property which will cause the text box to automatically expand to the available width?
The ListBoxItem's Content is left aligned by default. Add a Setter for the HorizontalContentAlignment to your ListBoxItem Style:
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="ContentTemplate">
...
</Setter>
</Style>

Dynamic width column xaml won't work

I have been trying to get dynamic column width to work for my simple WP8.1 app. The goal is to have the first column take up half of the listbox, and the other two columns a quarter each. I hoped to do this by assigning a dynamic width, using the * indicator as described here.
This let me to the piece of xaml code at the bottom of my post.
In my MainPage I set the DataContext to an ObservableCollection, and all the data shows up in their respective columns, the columns just do not get the desired width (they are all as small as can be).
What prevents my dynamic width from working? I have toyed around with HorizontalAlignment and Width of TextBoxes too, but with no success. I tried to look around for answers, and I even used some examples that did work, but that did not bring me closer to understanding why it does not work here.
Thanks in advance.
<Grid>
<ListBox Name="transactionListBox" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Name}"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Category}"/>
<TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding Amount}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
The item Grid itself is being sized-to-fit by the ListBox. You can override this behavior by setting the ItemContainerStyle to make the ListBoxItems stretch to fill horizontally:
<ListBox Name="transactionListBox" ItemsSource="{Binding}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
...
</ListBox>
I think you need a constraining width on the ListBox or Grid. With your code, the grid and ListBox will re-size to fit the content, so there is no need for the dynamic column definitions.

Freeze DataGrid Row

I was wondering if in a WPF datagrid in .net 4.0, is it possible to have a static row.
What I am trying to achieve is to create a static row (row 0), that will always be displayed at the top when the data grid is scrolled down.
The idea being that row 0 will always be in view as the user scrolls through the datagrid.
Thank you.
This "Simple solution" is only for a freezable footer, the frozen header solution would be a bit different (and actually much easier - just play with the HeaderTeamplate - put a stack panel with as many items stacked up as you want).
So I needed a footer row that is freezable, I couldn't find anything for months, so finally I decided to stop being lazy and investigate.
So if you need a footer, the gist is find a place in DataGrid's Template between the rows and the horizontal scrollviewer where you can squeeze extra Grid.Row with an ItemsControl with Cells.
PLAN OF ATTACK:
First, extract the DataGrid template (I used Blend). When getting familiarized with the template, note the parts in order:
PART_ColumnHeadersPresenter
PART_ScrollContentPresenter
PART_VerticalScrollBar
right under PART_VerticalScrollBar, there is a grid (I'll post it here for clarity)
<Grid Grid.Column="1" Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding NonFrozenColumnsViewportHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ScrollBar x:Name="PART_HorizontalScrollBar" Grid.Column="1" Maximum="{TemplateBinding ScrollableWidth}" Orientation="Horizontal" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}"/>
</Grid>
That's the grid I modified to include a "freezable/footer row". I am going to just hard-code colors, and replace Binding with hoperfully helpful "pretend" properties for simplicity (I'll mark them "MyViewModel.SomeProperty so they are easy to see):
<Grid Grid.Column="1" Grid.Row="2" x:Name="PART_DataGridColumnsVisualSpace">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=NonFrozenColumnsViewportHorizontalOffset}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ScrollBar Grid.Column="2" Grid.Row="3" Name="PART_HorizontalScrollBar" Orientation="Horizontal"
Maximum="{TemplateBinding ScrollableWidth}" ViewportSize="{TemplateBinding ViewportWidth}"
Value="{Binding Path=HorizontalOffset, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
<Border x:Name="PART_FooterRowHeader" Grid.Row="1" Height="30" Background="Gray" BorderBrush="Black" BorderThickness="0.5">
<TextBlock Margin="4,0,0,0" VerticalAlignment="Center">MY FOOTER</TextBlock>
</Border>
<ItemsControl x:Name="PART_Footer" ItemsSource="{Binding MyViewModel.FooterRow}"
Grid.Row="1" Grid.Column="1" Height="30">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="Gray" BorderThickness="0,0,0.5,0.5" BorderBrush="Black">
<!-- sticking a textblock as example, i have a much more complex control here-->
<TextBlock Text="{Binding FooterItemValue}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer x:Name="PART_Footer_ScrollViewer" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"
CanContentScroll="True" Focusable="false">
<StackPanel IsItemsHost="True" Orientation="Horizontal"/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
</Grid>
Also add to DataGrid respond to scroll and header resize
<DataGrid ... ScrollViewer.ScrollChanged="OnDatagridScrollChanged"
<Style TargetType="DataGridColumnHeader">
<EventSetter Event="SizeChanged" Handler="OnDataColumnSizeChanged"/>
</Style>
Now, back in .xaml.cs
Basically two main things are needed:
(1) sync column resize (so that corresponding footer cell resizes)
(2) sync DataGrid scroll with footer scroll
//syncs the footer with column header resize
private void OnDatagridScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (e.HorizontalChange == 0.0) return;
FooterScrollViewer.ScrollToHorizontalOffset(e.HorizontalOffset);
}
//syncs scroll
private void OnDataColumnSizeChanged(object sender, SizeChangedEventArgs e)
{
//I don't know how many of these checks you need, skip if need to the gist
if (!_isMouseDown) return;
if (!_dataGridLoaded) return;
if (!IsVisible) return;
var header = (DataGridColumnHeader)sender;
var index = header.DisplayIndex - ViewModel.NumberOfHeaderColumns;
if (index < 0 || index >= FooterCells.Count) return;
FooterCells[index].Width = e.NewSize.Width;
}
//below referencing supporting properties:
private ScrollViewer _footerScroll;
private ScrollViewer FooterScrollViewer
{
get {
return _footerScroll ??
(_footerScroll = myDataGrid.FindVisualChildByName<ScrollViewer>("PART_Footer_ScrollViewer"));
}
}
//added this so I don't have to hunt them down from XAML every time
private List<Border> _footerCells;
private List<Border> FooterCells
{
get
{
if (_footerCells == null)
{
var ic = myDataGrid.FindVisualChildByName<ItemsControl>("PART_Footer");
_footerCells = new List<Border>();
for (var i = 0; i < ic.Items.Count; i++)
{
var container = ic.ItemContainerGenerator.ContainerFromIndex(i);
var border = ((Visual)container).FindVisualChild<Border>();
_footerCells.Add(border);
}
}
return _footerCells;
}
}
that's it! I think the most important part is the XAML to see where you can put your "freezable row", everything else, like manipulating/sync'ing things is pretty easy - almost one liners)
I am not sure about the rows but you can freeze columns using FrozenColumnCount. This way it will always be visible. There should be a freeze property.

ScrollViewer in ItemsControl - ScrollBar not shown but working

This is not your average "My ScrollViewer isn't working" question...
Assume a window with a grid. The sizes of column 0 and row 1 are set to Auto, column 1 and row 0 are set to *. (important)
In cell [0, 0] there is an ItemsControl with a Template with a StackPanel inside a ScrollViewer inside a Grid. The reason is simple: Show a scroll bar if not all items in the ItemsControl can be displayed. The visibility of the vertical scrollbar is set to Auto (important).
In Cell [1, 1] there is a Button that displays its width.
If the window is too small too display all items in the ItemsControl this will lead to the following:
The scroll bar will be there but it is not visible. It is working, because I can scroll using the mouse wheel. The reason seems to be that the grid column in which the ItemsControl is contained is not automatically extended to make space for the scrollbar.
If I change (nearly) any of the parameters, the scroll bar is displayed as expected and the second column is reduced in size. Can anyone explain this odd behavior?
Additional Info:
The following parameter changes will lead to the scrollbar becoming visible:
Changing size of column 0 to *
Changing size of column 1 to Auto
Changing size of row 1 to *
Removing the Button.
Moving the Button to [0, 1] or [1, 0]
Manually setting the width of the ItemsControl.
Setting the VerticalScrollBarVisibility of the ScrollViewer in the ItemsControl to Visible.
However, changing the button in [1, 1] to something else, e.g. another ItemsControl doesn't change the strange behavior, so it has nothing to with the button. Furthermore, changing the width of the Button to something that is smaller than the second column, also doesn't remove that behavior.
Complete sample code for reproduction:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="343" Width="253">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ItemsControl>
<ItemsControl.Template>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel IsItemsHost="True" />
</ScrollViewer>
</Grid>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.Items>
<Button Content="Column1" Height="500" />
</ItemsControl.Items>
</ItemsControl>
<Button Content="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
Grid.Column="1" Grid.Row="1" />
</Grid>
</Window>
Looks like it might be a known bug in WPF. This question deals with a ListBox's ScrollViewer, but I think the principal is the same.
As an alternative, you could add something behind the ScrollViewer that has it's width bound to the ScrollViewer's ActualWidth, which will force the column to draw the correct size
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer x:Name="Test" Grid.Row="0" Grid.Column="0"
VerticalScrollBarVisibility="Auto">
<Button Content="Column1" Height="500" />
</ScrollViewer>
<Grid Grid.Column="0" Grid.Row="0"
Width="{Binding ElementName=Test, Path=ActualWidth}" />
<Button Content="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
Grid.Column="1" Grid.Row="1" />
</Grid>

Categories