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.
Related
I've a problem to connect.
I started to connect my tabs with a tabcontrol.ressources and it worked to show the text of each tabs.
Then I wanted to had a scroll for my TabItems and it doesn't work, nothing shows in tab... I can't even use tabcontrol.ressources anymore...
<DockPanel>
<Button Background="DarkGoldenrod" Height="Auto" Command="{Binding OpenFlyoutDataCommand}">
<StackPanel>
<materialDesign:PackIcon Kind="ArrowRightBoldCircleOutline" Width="30" Height="30"/>
</StackPanel>
</Button>
<TabControl ItemsSource="{Binding TabEDCWaferData, Mode=TwoWay}"
SelectedItem="{Binding SelectedTabEDCWaferData}">
<!-- Used to create a scroolbar for tabitems -->
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Hidden" >
<TabPanel Grid.Column="0" Grid.Row="0"
Margin="2,2,2,0" IsItemsHost="true"/>
</ScrollViewer>
<ContentPresenter ContentSource="..."/>
</Grid>
</ControlTemplate>
</TabControl.Template>
<!-- Contains the text in the tab item ! -->
<TabControl.Resources>
<DataTemplate DataType="TabItem">
<DockPanel>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Content}" />
</DockPanel>
</DataTemplate>
</TabControl.Resources>
</TabControl>
</DockPanel>
This is connected to a collection of TabItem, where I've a function to add Items binding to an other button.
private ObservableCollection<TabItem> _TabEDCWaferData;
public ObservableCollection<TabItem> TabEDCWaferData
{
get { return _TabEDCWaferData; }
set
{
_TabEDCWaferData = value;
RaisePropertyChanged("TabEDCWaferData");
}
}
public void AddTabItem(string name)
{
TabItem tab = new TabItem();
tab.Header = name;
tab.Content = "Temporary content";
TabEDCWaferData.Add(tab);
}
I read that I have to use the ContentPresenter, but I don't know how to bind it. I think this is not working with TabItems...
I just want to bind it as I did in the Ressources by using the ContentPresenter.
I hope that I'm clear enough ! Thanks
EDIT : I try to display in the ContentPresenter the selected item tab content that I add in the function `AddTabItem.
With ContentPresenter, most times, this does the job:
<ContentPresenter />
The default ContentSource is "Content". That means it'll look at the Content property of the templated parent and it'll take whatever it finds there for its own content.
But that doesn't help you at all, and you don't have to use ContentPresenter; it's just a convenience. In this case, the content you want to present is SelectedItem.Content, which isn't a valid ContentSource for ContentPresenter. But you can do the same thing with a binding on a ContentControl instead:
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ScrollViewer
Grid.Row="0"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Hidden"
>
<TabPanel
Grid.Column="0"
Margin="2,2,2,0" IsItemsHost="true"/>
</ScrollViewer>
<ContentControl
Grid.Row="1"
Content="{Binding SelectedItem.Content, RelativeSource={RelativeSource TemplatedParent}}"
/>
</Grid>
</ControlTemplate>
</TabControl.Template>
TemplateBinding isn't going to work with a Path such as "SelectedItem.Content"; it only accepts names of properties on the templated parent. I fixed your Grid.Row attributes, too.
Also, you may as well delete that DataTemplate for TabItem that you put in TabControl.Resources. That's not what DataTemplate is for; you use DataTemplates to define visual presentations for your viewmodel classes, but TabItem is a control. It already knows how to display itself, and in fact that DataTemplate is being ignored, so it's best not to leave it there; you'll only waste time later on making changes to it and trying to figure out why it's not having any effect. Your TabItems will display correctly without it.
Try something like this ?
<ContentPresenter Content="{TemplateBinding Content}" />
Edit
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" />
I've been trying to create a custom menu. For this reason I wanted to use an ItemsControl in order to make it flexible. After hours of headache I figured out how to make it - kinda.
I have my custom ItemsControl "LiftMenu" (which is not yet much custom but standard) and an UserControl called "LiftItem". Last but not least I got the Model-class "LiftMenuItem".
By adding a new LiftMenuItem to the LiftMenu, it should display a new LiftItem-control as corresponding item. So far so good, I managed to get this working.
In that LiftItem-control I bind like I would in a normal DataTemplate: plain bindings with a path, nothing more. Normally this would work just fine because the DataTemplate already has it's context set to the model-type.
But now I just get an empty control that does nothing and shows nothing, because the bindings don't work.
I implemented it this way:
<menu:LiftMenu HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinHeight="200" Background="#80A8A8A8" Margin="5,0,0,0">
<menu:LiftMenu.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</menu:LiftMenu.ItemsPanel>
<menu:LiftMenu.ItemTemplate>
<DataTemplate DataType="{x:Type menu:LiftMenuItem}">
<menu:LiftItem />
</DataTemplate>
</menu:LiftMenu.ItemTemplate>
<menu:LiftMenuItem Header="Test1"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border x:Name="border" BorderBrush="{Binding LabelColor}" BorderThickness="1" HorizontalAlignment="Left" Height="Auto" Margin="0"
VerticalAlignment="Stretch" Width="4" Background="{Binding BorderBrush, ElementName=border}"/>
<TextBlock Text="{Binding Header}" TextTrimming="CharacterEllipsis" Margin="5,0,5,0" HorizontalAlignment="Left" VerticalAlignment="Center"
Foreground="White" />
<controls:ProgressRing x:Name="ring" Grid.Column="2" HorizontalAlignment="Right" Stroke="#ffff8000" VerticalAlignment="Center"
Minimum="0" Maximum="100" Value="{Binding ProcessValue}" IsIndeterminate="{Binding ProcessIndeterminate}" Visibility="{Binding ProcessVisibility}"
Width="20" Height="20" Radius="10" Margin="2,0,2,0" />
</Grid>
In the end there is no text, no border. Just the ProgressRing is visible.
How can I fix this? This ListItem-control should become similiar to a button, thus I need to do some styling (animation, ...). I can't do this within a normal DataTemplate, but I don't want to miss the binding features of WPF on that. This would make it relatively unflexible.
What's the problem? I probably just miss some DataContext or so, but I don't know what it would be.
I have the following listbox in my windows phone 8.1 silverlight application
<ListBox x:Name="ScenarioControl" FontFamily="Segoe WP">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Name="myborder" Margin="0,0,0,0" BorderThickness="0,0,0,3" BorderBrush="Black">
<Grid HorizontalAlignment="Stretch" Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Tap="TextBlock_Tap" HorizontalAlignment="Left" VerticalAlignment="Center" Text="{Binding Path=Title, Mode=OneTime}" FontFamily="Segoe WP Semibold" Grid.Column="0" FontSize="23"/>
<Image Tap="Image_Tap" Source="/Images/blogger_small.png" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Column="1" OpacityMask="Black"/>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As you can see TextBlock bound dynamically. I need to change some properties for the last TextBlock element but I cannot find method to get exact element inside the code.
I did search and found that there are solutions with ItemContainerGenerator method but that does not work for me, the following line of code always returns null.
ListBoxItem item = this.ScenarioControl.ItemContainerGenerator.ContainerFromIndex(ScenarioControl.Items.Count - 1) as ListBoxItem;
The most common cause of this problem is that the item in question isn't visible when you make the call to ItemContainerGenerator. Most listboxes, etc. virtualize their contents, meaning that they don't generate UI for elements that would not be visible to the user, so when you go to get the container, you just get null.
This Question + Answer(s) sums this up, and offers a solution to the problem.
Using a converter can help. It's not all declarative, but was what I thought initially.
Change style of last item in ListBox
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.
I gave up on my code bugs so I decide to write here. I really hope to get a solution. What I want with the listbox is: when i click the button, it will retrieve data from database then load it into the listbox. It worked fine. But when I add wpf style, it started problem because I want to add image into each item next to text - image (right side) and text (left side). The result in the listbox is blank but actually it seems there is a data list - please look at the picture. I may have done something wrong in my code or wpf. I am not sure what is problem.... I would appreciate if you can have a look at my code. Your given code would be much helpful. Thanks alot!
WPF:
<ListBox Name="lstDinner" DisplayMemberPath="Name" Margin="513,85,608,445" Style="{DynamicResource ResourceKey=styleListBox}"/>
WPF STYLE:
<DataTemplate x:Key="templateListBoxItem">
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Border Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
Margin="0,0,10,0">
<Image Source="{Binding Path=Image}"
Stretch="Fill"
Height="40"
Width="40"></Image>
</Border>
<TextBlock Text="{Binding Path=Name}"
FontWeight="Bold"
Grid.Column="1"
Grid.Row="0"></TextBlock>
<TextBlock Text="{Binding Path=About}"
Grid.Column="1"
Grid.Row="1"></TextBlock>
</Grid>
</DataTemplate>
<Style x:Key="styleListBox" TargetType="{x:Type ListBox}">
<Setter Property="ItemTemplate" Value="{StaticResource ResourceKey=templateListBoxItem}"></Setter>
</Style>
C#:
private void Button_Click(object sender, RoutedEventArgs e)
{
_dinnerExtractor = new DinnerExtractor();
const int oneDay = 1;
_databaseDinnerList = new ObservableCollection<FoodInformation>(_dinnerExtractor.GetDinnerDays(oneDay));
if (_databaseDinnerList != null)
{
foreach (var list in _databaseDinnerList)
{
lstDinner.Items.Add(new FoodInformation { Dinner = list.Dinner, DinnerImage = list.DinnerImage});
}
//lstDinner.ItemsSource = _databaseDinnerList;
}
}
FoodInformation class does not contains properties: Image and Name (you are trying binding to these properties in DataTemplate).
From code-behind we can create definition of FoodInformation class with properties Dinner and DinnerImage:
class FoodInformation
{
public string Dinner { get; set; }
public ImageSource DinnerImage { get; set; }
}
So you should binding to properties Dinner and DinnerImage, not to Image and Name.
If you change in DataTemplate appropriate properties names everything will be ok.
<DataTemplate x:Key="templateListBoxItem">
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Border Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
Margin="0,0,10,0">
<Image Source="{Binding Path=DinnerImage }"
Stretch="Fill"
Height="40"
Width="40"></Image>
</Border>
<TextBlock Text="{Binding Path=Dinner }"
FontWeight="Bold"
Grid.Column="1"
Grid.Row="0"></TextBlock>
</Grid>
</DataTemplate>