I have a listview which is Grouped by Properties of the underlying Datasource.
The Groupstyle contains a Expander with an Header in which I want to display various things.
The listview shows Emails which are for example grouped by subject.
I know want to display (UnreadMailCount/Items) in each Group Header.
My Solution so far is:
<ListView Name="Mails" local:FM.Register="{Binding}" local:FM.GetFocus="Loaded"
Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
ItemsSource="{Binding Path=MailsProxy.View}"
SelectionMode="Single" SelectedItem="{Binding Path=SelectedMail, Mode=TwoWay}"
local:SortList.BringIntoViewSelected="True" local:SortList.IsGridSortable="True"
ItemContainerStyle="{StaticResource InboxMailItem}"
View="{Binding Source={x:Static session:Session.Current}, Path=InboxView.View}">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander Foreground="Black" BorderThickness="0,0,0,1" Style="{StaticResource ExpanderStyle}" Expanded="OnExpand" Collapsed="OnCollapse" Loaded="OnLoad">
<Expander.Header>
<DockPanel>
<TextBlock FontSize="14" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListView}, Path=DataContext.GroupBy}"/>
<TextBlock FontSize="14">:</TextBlock>
<TextBlock FontSize="14" Text="{Binding Path=Name, Converter={StaticResource GroupHeaderConverter}}" Margin="5,0,0,0"/>
<TextBlock> </TextBlock>
<TextBlock FontSize="14" Margin="0,1,0,0">(</TextBlock>
<TextBlock FontSize="14" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListView}, Path=DataContext.Unread}"/>
<!--<TextBlock FontSize="14" Text="{Binding StringFormat=0, Converter={StaticResource InboxGroupSeenConverter}}" />-->
<TextBlock FontSize="14">/</TextBlock>
<TextBlock FontSize="14" Text="{Binding Path=ItemCount}"/>
<TextBlock FontSize="14">)</TextBlock>
</DockPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
<ListView.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="DataContext" Value="{Binding Source={x:Static session:Session.Current}, Path=InboxView}"/>
</Style>
</ListView.Resources>
</ListView>
As you can see i tried to display the unread Mailitems by using a converter ( -->) this works, except, that the Header is not updated after the Property of the Mailitem is changed.
The Converter:
public class InboxGroupSeenConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return GetTotalUnread(value as CollectionViewGroup);
}
private static int GetTotalUnread(CollectionViewGroup group)
{
int count = 0;
foreach (eMail mailItem in group.Items)
if (mailItem.Seen == false)
count++;
return count;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Is there a way to do this with the Converter or should I go a different way by using Properteis as seen above ()
Edit: To clarify my Question. The Header of My group displays must display two different numbers. One being the ItemCount of the Group, the second being the number of Unread items within this group.
The number of unread Items is calculated within my converter which was a test for me, however this solution does not update the view if the underlying collection properties change.
I think that your Expander needs DynamicResource Style.
BTW Expander has it's own properties which you can use for your solution here is an example in the question I answered before
WPF-ListView-GridView-allow users...
good luck and let us know :-)
EDIT
tomorrow I'll come back to you, as I am finishing work soon :-), remember that expander has it's own itemsCount so there is no need to implement your own, second thing is to implement in your email object property bool Unread than just bind it to your textblock style using DataTriggers and voila!
Related
I have a ListBox with its ItemsSource bound to some UserControl-s and shown based on their current states(IsVisible)
Here is the code
<ListBox x:Name="sidebarList" Margin="0,0,10,10" ItemsSource="{Binding Modules, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0 5 0 5" Visibility="{Binding IsVisible, Mode=TwoWay}">
<TextBlock Text="{Binding Title}" FontWeight="Bold" />
<TextBlock Margin="0 5 0 0" MaxWidth="200" Foreground="Gray" Text="{Binding Detail}"
TextWrapping="WrapWithOverflow">
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="Collapsed">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
And now I'm trying to only get "IsVisible"/selected parts when a button is pressed. As of right now, the ListBox actually contains every option but just showing the IsVisible ones(at least that's what I think).
How do I only get the selected options? I have tried to use a new List with the same type as the UserControl-s type(which I placed in app.xaml.cs to call from anywhere) and add to that list when their state changes to IsVisible
but the List doesn't show up in my classes where I check them.
Sounds like what you really need is a view-model. Bind your Listbox to a view model that has an observable collection that only shows view-models of the items you want to present.
I can give you a more "correct" answer about how to do it with style/data-triggers but you should really just learn about MVVM and use a view-model to simplify.
I have the following TabConrol
<telerik:RadTabControl
Grid.Row="2" VerticalAlignment="Top" SelectedIndex="{Binding SelectedIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Content" DropDownDisplayMode="Visible" ScrollMode="Item" BorderThickness="0">
<telerik:RadTabItem DropDownContent="Job Config" Header="Job Config" >
<telerik:RadTabItem.Content>
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<local:JobGroupsCars DataContext="{Binding}" Margin="10" IsEnabled="{Binding Job.IsNotInEditMode ,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</ScrollViewer>
</telerik:RadTabItem.Content>
</telerik:RadTabItem>
<telerik:RadTabItem
DropDownContent="Job Info" Header="Job Info" >
<telerik:RadTabItem.Content>
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<local:JobDetailView DataContext="{Binding}" Margin="10" IsEnabled="{Binding Job.IsNotInEditMode ,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</ScrollViewer>
</telerik:RadTabItem.Content>
</telerik:RadTabItem>
<-- more tabs go here -->
</telerik:RadTabControl>
As you can see, the TabItem is a user control
What Im trying to achieve is when I click the save button I want to show the name that has validation errors
Im able to get all the validation errors but Im not able to show which tab has this error (as you can see in the below image)
I tried to use the following snippet but its not working
<UserControl.Resources>
<ControlTemplate x:Key="ValidationTabTemplate">
<DockPanel LastChildFill="True">
<Image Width="32" Height="32"
Source="../Assets/Delete_Icon.png" Opacity="0.75"
ToolTip="{Binding Path=AdornedElement.ToolTip, RelativeSource={RelativeSource AncestorType={x:Type Adorner}, Mode=FindAncestor}}"
/>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style x:Key="CustomTabError" TargetType="{x:Type telerik:RadTabItem}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="HeaderTemplate"
Value="{StaticResource ValidationTabTemplate}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
I am using WPF with MVVM
Any help would be appreciated
thanks alot
Okay a little more detailed than my comment:
You want to highight the Tabcontrol that containes your error if i understand you correctly:
Imagine this beeing your tabcontrol and the Checkboxes beeing your input fields we bound our tab item to each of our checkboxes.IsChecked via Multibinding
this would be your custom control and the .Validation.HasError property
if any of the CheckBoxes is not checked ( or in your case if an error occoured) the forground of the tabcontrol will become red by using a multiconverter
<TabControl>
<TabItem Header="Test">
<TabItem.Foreground>
<MultiBinding Converter="{StaticResource MultiEval}">
<Binding ElementName="CB1" Path="IsChecked"/>
<Binding ElementName="CB2" Path="IsChecked"/>
<Binding ElementName="CB3" Path="IsChecked"/>
</MultiBinding>
</TabItem.Foreground>
<StackPanel>
<CheckBox Name="CB1"></CheckBox>
<CheckBox Name="CB2"></CheckBox>
<CheckBox Name="CB3"></CheckBox>
</StackPanel>
</TabItem>
<Window.Resources>
<loc:MultiEvaluator x:Key="MultiEval"/>
</Window.Resources>
Converter:
public class MultiEvaluator : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
foreach (var value in values)
{
if (!System.Convert.ToBoolean(value))
{
return Brushes.Red;
}
}
return Brushes.Black;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
For the 2nd day I'm scouring the web and have not found a solution.
Take an element like this:
<TreeView ItemsSource="{Binding Types}" Width="300">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type models:Type}"
ItemsSource="{Binding SubTypes}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type SubType}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I use the Material NuGet library for base styles. Now however I need to disable the hover, select, etc. on the first level items and only allow the selection/hover for the subitems.
But everything I seem to find is about styling the contents of each item or styling everything globally.
A <- remove selection/hover (pref single click too but that's another topic)
A1 <- maintain original style, hover and select
A2 <- maintain original style, hover and select
A3 <- maintain original style, hover and select
B <- remove selection/hover (pref single click too but that's another topic)
B1 <- maintain original style, hover and select
B2 <- maintain original style, hover and select
B3 <- maintain original style, hover and select
Sounds like you don't really want each top-level item to act like a normal TreeViewItem. In that case, why not move the top-level items outside of the TreeView?
Basically, you'd have an ItemsControl of the top-level items, where the item template acts a bit like an Expander containing a TreeView of the items underneath it. You could style the top-level items to look however you like.
The downside would be that the trees under the top-level items would be virtualized individually, not as a whole. That is, they would not share containers. Unless you have a ton of top-level items, that probably won't be a big deal.
Example:
<ItemsControl xmlns:s="clr-namespace:System;assembly=mscorlib"
ItemsSource="{Binding Types}">
<ItemsControl.Resources>
<ControlTemplate x:Key="ExpanderButtonTemplate" TargetType="ToggleButton">
<Border Background="Transparent" Padding="3,2">
<ContentPresenter />
</Border>
</ControlTemplate>
<Style TargetType="Expander">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Expander">
<DockPanel LastChildFill="True">
<ToggleButton DockPanel.Dock="Top"
IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
Template="{StaticResource ExpanderButtonTemplate}">
<ContentPresenter ContentSource="Header" />
</ToggleButton>
<Border>
<ContentPresenter x:Name="contentSite" Visibility="Collapsed" />
</Border>
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter TargetName="contentSite" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Name}">
<TreeView ItemsSource="{Binding SubTypes}" BorderThickness="0" Padding="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type models:Type}"
ItemsSource="{Binding SubTypes}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type models:SubType}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Click on one of the top-level items to expand the tree beneath it.
You can keep the TreeView and set properties or apply a style to your top-level items (assuming your TreeView isn't nested in another TreeView) by searching up the logical hierarchy for a TreeViewItem:
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="{x:Null}">
<Setter Property="Background" Value="LightYellow" />
</DataTrigger>
For top-level items this binding spams warnings to debug output, but they can safely be ignored. A more sophisticated version of this trick would be to create an inheritable attached property TreeViewItemLevel that would be set to zero on the TreeView and to one more than the inherited value on TreeViewItems.
While setting a DataTrigger with a relative source binding works, it will produce binding errors.
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="{x:Null}">
By setting a FallbackValue of x:Null, you will not get any binding errors, but warnings.
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}, FallbackValue={x:Null}}" Value="{x:Null}">
An alternative approach that will neither yield errors nor warnings, is to create a value converter that essentially does the same a RelativeSource binding, but will handle the errors, too. It returns true for any dependency object passed in, if it has an ancestor of type TreeViewItem, false otherwise.
public class IsRootTreeViewItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is null ? Binding.DoNothing : !HasTreeViewItemAncestor((DependencyObject)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
private static bool HasTreeViewItemAncestor(DependencyObject child)
{
var parent = VisualTreeHelper.GetParent(child);
return parent switch
{
null => false,
TreeViewItem _ => true,
_ => HasTreeViewItemAncestor(parent)
};
}
}
In your TreeViewItem style trigger, you can use the converter by binding to the item itself with Self.
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource IsRootTreeViewItemConverter}}" Value="True">
This approach works for both statically assigned TreeViewItems and ItemsSource bindings.
I try to group some data in wpf datagrid .. it works fine, except the HEADER OF TEMPLATE DOESN'T DISPLAY any thing. It should display teacher name by TEACHER property. I use SQtOLinq as the underlayer data source.
what did I miss in my code?
XAML code:
<DataGrid AutoGenerateColumns="False" Height="311" Style="{StaticResource DashboardGridStyle}" HorizontalAlignment="Left" Name="TeacherDetailsDG" VerticalAlignment="Top" Width="322" ItemsSource="{Binding}" Margin="178,0,0,0" SelectionChanged="TeacherDetailsDG_SelectionChanged" RowBackground="#FF00E700" SelectionUnit="FullRow" BorderBrush="#FF00E400" AlternatingRowBackground="#FFC4B5B5">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel >
<TextBlock Text="{Binding Path=Teacher,Mode=TwoWay}" Foreground="Blue"/>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander x:Name="exp">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Teacher,Mode=TwoWay}" />
</StackPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter Visibility="{Binding ElementName=exp, Path=IsExpanded}" />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=TeacherID}" Visibility="Hidden">
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Path=Teacher}"/>
<!--Grades column-->
<DataGridComboBoxColumn x:Name="GradesCombo" ItemsSource="{Binding}" DisplayMemberPath="Grade" SelectedValuePath="ID" SelectedValueBinding="{Binding Path=GradeID}"></DataGridComboBoxColumn>
<!--Subjects column-->
<DataGridComboBoxColumn x:Name="SubjectsCombo" ItemsSource="{Binding}" DisplayMemberPath="Subject" SelectedValuePath="ID" SelectedValueBinding="{Binding Path=SubjectID}"></DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
Datagrid Binding code:
public MainWindow()
{
InitializeComponent();
GradesCombo.ItemsSource = SchoolDC.GradesTables;
SubjectsCombo.ItemsSource = SchoolDC.SubjectsTables;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView((SchoolDC.TeachersDetailsSP().ToList<object>()));
view.GroupDescriptions.Add(new PropertyGroupDescription("Teacher"));
TeacherDetailsDG.ItemsSource = view;
}
catch (Exception)
{
throw;
}
}
This is the result:
GroupItem.Name gets the value of the property you grouped by .
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" Foreground="Blue"/>
</DataTemplate>
If you are using ReSharper, (and you most definitely are, aren't you?) the problem with the accepted answer is that the reference to Name will be flagged with "Cannot resolve property 'Name' in data context of type 'Xyz'" where Xyz is the type of the enclosing data context, most probably the ViewModel of your screen.
(Or, your enclosing data context might also have a property called 'Name', in which case you will be oblivious to the discrepancy.)
To remedy this problem, change <DataTemplate> to <DataTemplate DataType="GroupItem"> to let WPF (and ReSharper) know what is the actual type of the data context that the DataTemplate is going to be applied to.
Then, the Name property will not be flagged anymore, and as a matter of fact, if you choose "Go to definition" on the property you will be taken to the Name property of UIElement, from which GroupItem inherits it. (Assuming that you are, of course, using ReSharper, instead of just plain woefully inadequate Visual Studio, right?)
I'm creating a file explorer app using the TreeView control provided by WPF. I've customized it such that it displays a file icon next to the path. I'm using a IValueConverter to convert the path to the required image, which I based off this page: http://www.codeproject.com/Articles/21248/A-Simple-WPF-Explorer-Tree
It mostly works! Except that the root TreeViewItems don't display icons for some reason. I placed a breakpoint on the IValueConverter::Convert() function and confirmed that it doesn't get executed for the root nodes, but do for all children nodes after that.
The VS output window shows no binding errors, so I'm at a loss at how this could happening. Any ideas?
Converter Code:
[ValueConversion(typeof(string), typeof(bool))]
public class HeaderToImageConverter : IValueConverter
{
public static HeaderToImageConverter Instance = new HeaderToImageConverter();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((value as string).Contains(#"\"))
{
Uri uri = new Uri("pack://application:,,,/Images/diskdrive.png");
BitmapImage source = new BitmapImage(uri);
return source;
}
else
{
Uri uri = new Uri("pack://application:,,,/Images/folder.png");
BitmapImage source = new BitmapImage(uri);
return source;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException("Cannot convert back");
}
}
TreeView XAML:
<TreeView Grid.Column="0" Name="fileExplorer" Margin="8,8,8,8">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Width="20" Height="20" Stretch="Fill" Source="{Binding Converter={x:Static local:HeaderToImageConverter.Instance}}" />
<TextBlock Text="{Binding}" Margin="5,0" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.Resources>
I ran into a similar problem. But besides the TreeView.Resources I had the TreeView.ItemContainerStyle element as well. Moving the Style element to there actually styled only the root, but not the children. Therefore, a solution would be to put the element in both places.
TreeView XAML:
<TreeView Grid.Column="0" Name="fileExplorer" Margin="8,8,8,8">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Width="20" Height="20" Stretch="Fill" Source="{Binding Converter={x:Static local:HeaderToImageConverter.Instance}}" />
<TextBlock Text="{Binding}" Margin="5,0" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Width="20" Height="20" Stretch="Fill" Source="{Binding Converter={x:Static local:HeaderToImageConverter.Instance}}" />
<TextBlock Text="{Binding}" Margin="5,0" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.Resources>
Now, it would be nicer not to have to reuse this HeaderTemplate Setter. But I have not found a way to do this.