GroupBox Header Not Showing - c#

I followed an this StackOverflow question, which has a link to another article, on how to group data, but my header is not showing. Can someone point me to the problem of why my header is not showing in the grouping of my data. The only thing different between the article and my markup that I see is that my data is in a different format, but I have no control on how I receive it.
Representation of my data:
<MyDetails xmlns="clr-namespace:MyCompany.Mapping;assembly=Mapping" xmlns:scg="clr-
namespace:System.Collections.Generic;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<MyDetails.Details>
<scg:List x:TypeArguments="MyAttributes" Capacity="64">
<MyAttributes Bit="0" Channel="1" Name="SomeName" IOAttribute="O" />
</scg:List>
</MyDetails.Details>
</MyDetails>
ViewModel Property I bind to:
ObservableCollection<MyDetails> Channels= new
ObservableCollection<MyDetails>();
MyDetails Class Public Property (Defined as a DataMember):
List<MyAttributes> Details = new List<MyAttributes>();
MyAttributes Class Public Properties (All defined as a DataMembers):
property string Channel {get; set;}
property string Bit {get; set;}
property string Name {get; set;}
property string Attribute {get; set;}
XAML Resource Header:
<CollectionViewSource x:Key="MyDetails" Source="{Binding Channels[0].Details}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Channel" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
XAML:
<ItemsControl ItemsSource="{Binding Source={StaticResource MyDetails}}">
<ItemsControl.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<GroupBox Margin="10" >
<GroupBox.Header>
<TextBlock Text="{Binding Channel}" FontWeight="Bold" FontSize="16" /> <!-- Does not show -->
</GroupBox.Header>
<ItemsPresenter />
</GroupBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Bit}" Width="50" />
<TextBlock Text="{Binding Path=Name}" Width="150" />
<TextBlock Text="{Binding Path=Attribute}" Width="50" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

DataContext against each GroupItem will be of CollectionViewGroup type which contains information about each group like Items or Name. You need to change binding in a group header to Name which will be the value grouped by
<GroupBox.Header>
<TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="16" />
</GroupBox.Header>

Related

Why are Groups in WPF ListView empty?

I have a class in my application with three properties GroupName, ItemName, and Data. I am have a ListView to group a collection of these classes by GroupName and display their ItemName property in a text box. The problem is that when I run the code, the groups appear correctly but none of them display any members.
Here is the xaml code:
<ListView x:Name="MyList">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander IsExpanded="True">
<Expander.Header>
<TextBlock FontWeight="Bold" Text="{Binding Name}"/>
</Expander.Header>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type testProgram:MyClass}">
<TextBlock Text="{Binding ItemName}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And here is the code behind:
public partial class MyListView
{
public MyListView(ObservableCollection<MyClass> items)
{
InitializeComponent();
Items = items;
var v = CollectionViewSource.GetDefaultView(Items);
v.GroupDescriptions.Add(new PropertyGroupDescription("GroupName"));
MyList.ItemsSource = v;
}
public ObservableCollection<MyClass> Items { get; set; }
}
When I remove the <ListView.GroupStyle>... and set MyList.ItemsSource = Items; then everything shows up properly.
I suspect that the issue is between ItemsSource = v, DataType = "{x:Type testProgram:MyClass}", and {Binding ItemName}, but I don't know what is breaking or how to fix it.
Your ControlTemplate is missing an ItemsPresenter. WPF uses an ItemsPresenter in the GroupItem template to mark the location where the actual items will be placed when the template is expanded. Since you have no presenter, the detail items are not displayed.
Try changing your template to this:
<Expander IsExpanded="True">
<Expander.Header>
<TextBlock FontWeight="Bold" Text="{Binding Name}"/>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>

Updating TabControl ItemSource ViewModels when specific state changes

I have a TabControl bound to an ObservableCollection property of view models, TabViewModelsCollection.
When another property gets set from within the view models, DeviceState, I'd like to raise a property changed event and tell my TabControls ItemSource to refresh.
The problem is my ItemSource is an ObservableCollection of ViewModels and when I call RaisePropertyChanged("TabViewModelsCollection"); nothing gets updated.
Furthermore my tab views contain multiple user controls and bindings.
The scenario that is supposed to play out is: A device is located on the network, data is collected, and the tabs of device information should then be updated.
Currently my TabControl only updates when I select a different device then select the device whos information I want to see. Think left panel list of devices, right panel with tabs of device info.
Let me know what part of the code you guys might want to see, my codebase is quite large so it would be hard to post it.
Here is where the TabControl is defined in my view:
<!-- Devist List Controls -->
<Grid DockPanel.Dock="Left" Margin="5,5">
<local:DeviceListView DataContext="{Binding DeviceListViewModel}" Grid.Row="0"/>
</Grid>
<GroupBox Header="Device Information" DockPanel.Dock="Right" Margin="0,0,5,5">
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding DeviceListViewModel.SelectedDevice.TabViewModelsCollection}" SelectedItem="{Binding DeviceListViewModel.SelectedDevice.SelectedTabItemVm}" >
<TabControl.Resources>
<DataTemplate DataType="{x:Type vms:HomeViewModel}">
<local:HomeTab/>
</DataTemplate>
<DataTemplate DataType="{x:Type vms:ConfigurationViewModel}">
<Grid>
<local:ConfigurationFileView Visibility="{Binding Configuration, TargetNullValue=Collapsed, FallbackValue=Visible}"/>
<local:ErrorTab Visibility="{Binding Path= Configuration, TargetNullValue=Visible, FallbackValue=Hidden}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type vms:ExpansionModulesViewModelFactory}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="35"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<DockPanel >
<local:ExpansionModulesList Title="Discovered/Enumerated"
DataContext="{Binding DiscoveredModules}"
/>
<GridSplitter Width="5"/>
<local:ExpansionModulesList Title="User Action Required"
DataContext="{Binding FaultyModules}"
/>
</DockPanel>
</StackPanel>
<DockPanel Grid.Row="1">
<StackPanel Orientation="Horizontal" FlowDirection="RightToLeft" Margin="5" IsEnabled="{Binding IsCommandEnabled}">
<Button Content="Cancel" HorizontalAlignment="Right"
Command="{Binding CancelExpansionCommand }"
ToolTip="Revert all local modifications by refreshing data from the controller." />
<Separator Width="10"/>
<Button Content="Apply" HorizontalAlignment="Center"
Command="{Binding ApplyExpansionCommand }"
ToolTip="Apply all changes to the controller." />
<Separator/>
</StackPanel>
</DockPanel>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type vms:LogViewModel}">
<local:LogView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:SignalStrengthViewModel}">
<local:SignalStrengthView />
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
<Setter Property="Header" Value="{Binding Name}" />
</Style>
</TabControl.ItemContainerStyle>
EDIT: I want to be able to call raised property changed on this and have it refresh all of my tab views..
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding DeviceListViewModel.SelectedDevice.TabViewModelsCollection}" SelectedItem="{Binding DeviceListViewModel.SelectedDevice.SelectedTabItemVm}" >
RaisePropertyChanged("TabViewModelsCollection");
Hi please try the next solution:
VM code redaction
private State _deviceState;
private ObservableCollection<object> _tabViewModelsCollection;
public State DeviceState
{
get { return _deviceState; }
set
{
_deviceState = value;
RaisePropertyChanged("DeviceState");
UpdateTabViewModelsCollection();
}
}
public ObservableCollection<object> TabViewModelsCollection
{
get
{
return _tabViewModelsCollection ??
(_tabViewModelsCollection = new ObservableCollection<object>(GetDeviceData()));
}
}
private void UpdateTabViewModelsCollection()
{
_tabViewModelsCollection = null;
RaisePropertyChanged("TabViewModelsCollection");
}
private List<object> GetDeviceData()
{
//implement here the data collection process
throw new NotImplementedException();
}
Xaml redaction (define the UpdateSourceTrigger)
ItemsSource="{Binding DeviceListViewModel.SelectedDevice.TabViewModelsCollection, UpdateSourceTrigger=PropertyChanged}"
Let me know if it was helpful.
Regards.

Not able to show grouped data correctly in WPF DataGrid

I am trying to display my EF query result (which has a group by clause) and display itinto a DataGrid. I am trying to copy the example here
http://wpftutorial.net/DataGrid.html
So here is my XAML code
<DataGrid ItemsSource="{Binding QueryResult}">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=ItemCount}"/>
<TextBlock Text="Items"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
Here is my ViewModel
internal void GenerateReport()
{
Data = Project.GetReport(); **// this is List<IGrouping<string, MyType>>**
//QueryResult is a ListCollectionView property
QueryResult= new ListCollectionView(Data);
QueryResult.GroupDescriptions.Add(new PropertyGroupDescription("train"));
}
This is what my result of query looks like
I do get the key correctly but if you look at my code, I have a collection against the key item. THat is not displaying here. How do I get that to display against each train?
EDIT
I think I have more details on this. Here is how my QueryResult collection looks like
Basically my QueryResult has 10 items where each item has a Name, Items and ItemsCounts.
Each Items itself has 1 item inside which has a key and Group. This group basically contains the items that I want to display in my DataGrid as grouped against parent 10 records that are appearing.

Building a list of options with MVVM

I have a ObservableCollection> property that I would like to display in a list box in a WPF window using the MVVM model. Ideally I would like to have a listbox that has a check box for the bool and a label for the string and when the user changes the value of the checkbox it would change the corresponding bool. What is the best way to go about doing this?
Try using an ItemsControl, and don't bind to a list of checkboxes, define a model:
<ItemsControl ItemsSource="{Binding YourModelList}">
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Selected}"/>
<TextBlock Text="{Binding Path=Description}" />
</StackPanel>
</ItemsPanelTemplate>
</ItemsControl>
In the ViewModel:
public ObservbleCollection<YourModel> YourModelList { get; set; }
And the Model:
public class YourModel
{
public bool Selected {get;set;}
public string Description {get;set;}
public int ID {get;set;}
}
Implement INotifyPropertyChanged as necessary
Idealy, if you want to follow the MVVM approach, everyone of your elements in the ObservableCollection should be a ViewModel.
These viewModels should expose 2 properties, such as string Description and bool IsSelected.
All you need then is to provider your ListBox with a Style in order to display a checkbox and textblock for each databound ViewModel.
The following XAML implements such a Style. Note: The usercontrol DataContext should hold a ViewModel containing an ObservableCollection<YourClass> Items { get; set; } property where YourClass exposes string Description { get; set; } and bool IsSelected { get; set; }. You will obviously want to throw in some INotifyPropertyChanged magic in there.
<Grid>
<Grid.Resources>
<Style x:Key="CheckBoxListStyle" TargetType="ListBox">
<Style.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="itemChoix" Margin="5,5,0,0" IsChecked="{Binding IsSelected, Mode=TwoWay}" IsEnabled="{Binding IsEnabled, Mode=TwoWay}" />
<TextBlock Margin="5,5,0,0" Text="{Binding Description, Mode=TwoWay}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="BorderThickness" Value="0" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="1" Style="{StaticResource BoxBorder}" Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Grid>
<ListBox ItemsSource="{Binding Path=Items}" SelectionMode="Multiple" Style="{StaticResource CheckBoxListStyle}"/>
</Grid>
</Border>
</Grid>
Create your public ObservableCollection property in your ViewModel
Create an IsSelected bool property.
Populate collection from constructor or command.
Create a command for the checkbox that sets the IsSelected property.
Do the Xaml binding

WPF - Change a style in code behind

I have a list box that displays the results of a TFS Query. I want to change the style of the ListBoxItem in the code behind to have the columns that are included in the query results.
The style for the ListBoxItem is defined in my Windows.Resoruces Section. I have tried this:
public T GetQueryResultsElement<T>(string name) where T : DependencyObject
{
ListBoxItem myListBoxItem =
(ListBoxItem)(lstQueryResults.ItemContainerGenerator.ContainerFromIndex(0));
// Getting the ContentPresenter of myListBoxItem
ContentPresenter myContentPresenter =
myListBoxItem.Template.LoadContent().FindVisualChild<ContentPresenter>();
// Finding textBlock from the DataTemplate that is set on that ContentPresenter
DataTemplate myDataTemplate = myContentPresenter.ContentTemplate; <------+
T myControl = (T)myDataTemplate.FindName(name, myContentPresenter); |
|
return (T)myControl; |
} |
|
ContentTemplate is null ----------------------------------------------+
But the ContentTemplate is null. I got that code from here, then modified it with the LoadContent call (the orginal code gave null for the ContentPresenter).
Anyway. If you know a way to change an existing style in the code behind I would love to see it.
Specifics if you want them:
I am going for WrapPanel in my ListBoxItem Style. This is what I want to add the extra TextBlock items to.
Here is part of my style:
<!--Checkbox ListBox-->
<Style x:Key="CheckBoxListStyle" TargetType="ListBox">
<Style.Resources>
<Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem">
<Setter Property="Tag" Value="{Binding Id}"/>
<Setter Property="Background">
<Setter.Value>
<Binding Path="Type" Converter="{StaticResource WorkItemTypeToColorConverter}" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border BorderThickness="1" BorderBrush="#D4D4FF">
<Grid Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WrapPanel}}, Path=ActualWidth}" ScrollViewer.CanContentScroll="True" Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<Grid.Background>
<Binding Path="Type" Converter="{StaticResource WorkItemTypeToColorConverter}" />
</Grid.Background>
<CheckBox VerticalAlignment="Center" Grid.Column="0" IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay}" Name="chkIsSelected" />
<WrapPanel Grid.Column="1" Margin="5,0,5,0" Name="QueryColumns">
<TextBlock VerticalAlignment="Center" Text="{Binding Id}" Name="txtID" />
<TextBlock VerticalAlignment="Center" Margin="5,0,5,0" Text="{Binding Title}" Name="txtTitle" />
</WrapPanel>
You're going against the grain here, trying to manipulate visual elements directly in code-behind. There's a much simple solution involving data binding.
I'll provide the general solution because I don't know the specifics of your solution.
Once you get your query results, create an enumeration that returns a column name, and a field value for each iteration.
Example:
class NameValuePair
{
public string Name { get; set; }
public object Value { get; set; }
}
public IEnumerable<IEnumerable<NameValuePair>> EnumerateResultSet(DataTable resultSet)
{
foreach (DataRow row in resultSet.Rows)
yield return EnumerateColumns(resultSet, row);
}
public IEnumerable<NameValuePair> EnumerateColumns(DataTable resultSet, DataRow row)
{
foreach (DataColumn column in resultSet.Columns)
yield return new NameValuePair
{ Name = column.ColumnName, Value = row[column] };
}
And in your code-behind, once you get your DataTable result set, do this:
myResultsList.ItemsSource = EnumerateResultSet(myDataTable);
The XAML might look like this:
<Window.Resources>
<DataTemplate x:Key="ColumnTemplate">
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="2" Padding="2">
<WrapPanel>
<TextBlock Text="{Binding Name}" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Value}" Margin="0,0,10,0"/>
</WrapPanel>
</Border>
</DataTemplate>
<DataTemplate x:Key="RowTemplate">
<Grid>
<ItemsControl
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ColumnTemplate}"
Margin="0,5,0,5"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Name="myResultsList" ItemTemplate="{StaticResource RowTemplate}"/>
</Grid>
Sample output:

Categories

Resources