Variable background color in an itemscontrol - c#

I would like to create an items control that alternates the background color of an item based on the alternation index of the group it belongs to. In reference to the classes I list below, I would like it to, for example, have the background of the first three RandomHouse books as black, and then when it encounters the next Publisher, the background changes back to white, and so on and so forth for as many unique publishers there are. The number and name of the publishers is nondeterministic and is only evaluated at runtime. I have tried to do it with xaml to the best of my ability but it doesnt seem the alternationindex can be accessed for a GroupItem for whatever reason. Any help would be appreciated.
class Book
{
String Publisher {get; set;}
String Title {get; set;}
}
class ViewModel
{
var listBooks = new ObservableCollection<Book>();
listBooks.Add(new Book(){Publisher = "RandomHouse", Title = "Title1"});
listBooks.Add(new Book(){Publisher = "RandomHouse", Title = "Title2"});
listBooks.Add(new Book(){Publisher = "Penguin", Title = "Title5"});
ObservableCollection<Book> ListBookItems {get {return listBooks.Orderby(e => e.Publisher).ToList(); } }
}
<UserControl.Resources>
<Style TargetType="ItemsControl" x:Key="ListBookStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ScrollViewer CanContentScroll="True">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="FontFamily">
<Setter.Value>Consolas</Setter.Value>
</Setter>
</Style>
<DataTemplate DataType="{x:Type models:Book}">
<Grid IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Publisher" Width="100"/>
<ColumnDefinition SharedSizeGroup="Title" Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock
HorizontalAlignment="Left"
Text="{Binding Publisher}"
Grid.Column="0"
FontWeight="Bold"
Margin="5"/>
<TextBlock
HorizontalAlignment="Left"
Text="{Binding Title}"
Grid.Column="1"
FontWeight="Bold"
Margin="5"
/>
</Grid>
</DataTemplate>
<CollectionViewSource x:Key="ListBookItems" Source="{Binding ListBookItems}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Publisher"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</UserControl.Resources>
<DockPanel>
<ItemsControl
ItemsSource="{Binding Source={StaticResource ListBookItems}}"
Style="{StaticResource ListBookStyle}">
<ItemsControl.GroupStyle>
<GroupStyle AlternationCount="2">
<GroupStyle.ContainerStyle >
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Foreground" Value="#FF444444"/>
<Setter Property="Background" Value="#FF000000"/>
<!--<Style.Triggers>
<Trigger Property="AlternationIndex" Value="0">
<Setter Property="Foreground" Value="#FF444444"/>
<Setter Property="Background" Value="#FFD9D9D9"/>
</Trigger>
<Trigger Property="AlternationIndex" Value="1">
<Setter Property="Foreground" Value="#FF444444"/>
<Setter Property="Background" Value="#FFEFEFEF"/>
</Trigger>
</Style.Triggers>-->
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer CanContentScroll="True">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DockPanel>

You are very close. You just need to specify ItemsControl.AlternationIndex as the property name in each trigger, instead of just AlternationIndex.
That said, frankly I had second-thoughts about even proposing this as an answer, except that I can't actually get that to work. That is, while I can see in the debugger that it correctly sets the GroupItem.Background property value as desired, I see no visible effect on-screen. It's as if the items-presenter for the group is ignoring the GroupItem's background property.
The reason I'm going ahead and posting this as an answer even though it doesn't completely address the issue is that getting the GroupItem.Background property value to be respected by the actual presenter in the GroupItem is a completely different problem than correctly using the AlternationIndex value. So while you can use this answer to get the AlternationIndex value to be correctly bound, you will need to do some more work and/or post another question to dig into why having correctly set the Background property value, that does not actually change the background as shown on the screen.
I wish I could also explain how to get the background property in GroupItem to affect the display on the screen. I hope that if you figure that out, you will at least follow up with a comment here, explaining the answer. :) (Or if you post a question and someone else explains it, comment with a reference to that answer).

Related

C# WPF The global style not working in some parts of the code [duplicate]

I am trying to learn something about WPF and I am quite amazed by its flexibility.
However, I have hit a problem with Styles and DataTemplates, which is little bit confusing.
I have defined below test page to play around a bit with styles etc and found that the Styles defined in <Page.Resources> for Border and TextBlock are not applied in the DataTemplate, but Style for ProgressBar defined in exactly the same way is applied.
Source code (I just use Kaxaml and XamlPadX to view the result)
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="Background" Value="SkyBlue"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="CornerRadius" Value="5"/>
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontWeight" Value="Bold"/>
</Style>
<Style TargetType="{x:Type ProgressBar}">
<Setter Property="Height" Value="10"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Foreground" Value="Red"/>
</Style>
<XmlDataProvider x:Key="TestData" XPath="/TestData">
<x:XData>
<TestData xmlns="">
<TestElement>
<Name>Item 1</Name>
<Value>25</Value>
</TestElement>
<TestElement>
<Name>Item 2</Name>
<Value>50</Value>
</TestElement>
</TestData>
</x:XData>
</XmlDataProvider>
<HierarchicalDataTemplate DataType="TestElement">
<Border Height="45" Width="120" Margin="5,5">
<StackPanel Orientation="Vertical" Margin="5,5" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Text="{Binding XPath=Name}"/>
<ProgressBar Value="{Binding XPath=Value}"/>
</StackPanel>
</Border>
</HierarchicalDataTemplate>
</Page.Resources>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
<Border Height="45" Width="120" Margin="5,5">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Text="Item 1"/>
<ProgressBar Value="25"/>
</StackPanel>
</Border>
<Border Height="45" Width="120" Margin="5,5">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Text="Item 2"/>
<ProgressBar Value="50"/>
</StackPanel>
</Border>
</StackPanel>
<ListBox Margin="10,10" Width="140" ItemsSource="{Binding Source={StaticResource TestData}, XPath=TestElement}"/>
</StackPanel>
</Page>
I suspect it has something to do with default styles etc, but more puzzling is why some Styles are applied and some not. I cannot find an easy explanation for above anywhere and thus would like to ask if someone would be kind enough to explain this behaviour in lamens' terms with possible links to technical description, i.e. to MSDN or so.
Thanks in advance for you support!
I discovered a simple workaround for this. For any elements that are not able to search outside the data template encapsulation boundary (i.e. are not being implicitly styled), you can just declare an empty style within the data template for that element type and use the BasedOn attribute of the style to find the correct implicit style outside the data template to apply.
In the example below, the TextBox is able to search outside the data template encapsulation boundary (because it inherits from Control?), but the TextBlock is not able to, so I declare the empty style for it which can search outside the data template.
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}" />
</DataTemplate.Resources>
<DockPanel>
<TextBlock Text="{Binding Name}" />
<TextBox Text="{Binding Value}" />
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
This is actually by design. Elements that do not derive from Control will not pick up implicit Styles, unless they are in the application resources.
This link explains this in more detail, or you can view the Connent bug report.
I've looked into this also, and I personally think it's a bug. I've noticed that the style is set if you name your styles like so:
<Style x:Key="BorderStyle" TargetType="{x:Type Border}">
etc...
and explicitly set your DataTemplate to use those styles:
<HierarchicalDataTemplate DataTemplate="TestElement">
<Border Height="45" Width="120" Margin="5,5", Style="{StaticResource BorderStyle}">
I think that it's possible that for DataTemplates (and maybe ControlTemplates), they default to having a null style, unless you explicitly set them.
That to me is not meant to happen - it's not a logical way of WPF working...
This is because ListBox is a logical parent of your datatemplate items, now remember, all properties those are "inheritable" like font, forecolor etc, are derived from the logical parent and ListBox already overrides it in its own default style, thats why this will not work. However in this case, you can use named styles as Mr. Dave has suggested, but I think if it does not work then this is a known problem in case of List Box etc, you can refere to my question here, i had similar problem in listbox, and the answers in my question are in more detail.

How to persistently change color of ListBox SelectedItem after selecting

I have a listbox that loads it's items with Foreground color set to red. What I'd like to do is: upon selecting an item with the mouse, change the foreground color of SelectedItem to black, but make the change persistent so that after deselecting the item, color remains black. Incidentally I want to implement this as a way of showing 'read items' to the user.
Essentially I want something like an implementation of the common property trigger like the code below, but not have the style revert after deselection. I've played around with event triggers as well without much luck.
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True" >
<Setter Property="Foreground" Value="Black" /> //make this persist after deselection
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
Thanks in advance!
You could animate the Foreground property:
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Foreground" Value="Red" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(ListBoxItem.Foreground).(SolidColorBrush.Color)"
To="Black" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
The downside of this simple approach is that the information is not stored somewhere. This is pure visualization without any data backing. In order to persist the information, so that restarting the application shows the same previous state, you should introduce a dedicated property to your data model e.g IsMarkedAsRead.
Depending on your requirements, you can override the ListBoxItem.Template and bind ToggleButton.IsChecked to IsMarkedAsRead or use a Button which uses a ICommand to set the IsMarkedAsRead property. There are many solutions e.g. implementing an Attached Behavior.
The following examples overrides the ListBoxItem.Template to turn the ListBoxItem into a Button. Now when the item is clicked the IsMarkedAsRead property of the data model is set to true:
Data model
(See Microsoft Docs: Patterns - WPF Apps With The Model-View-ViewModel Design Pattern for an implementation example of the RelayCommand.)
public class Notification : INotifyPropertyChanged
{
public string Text { get; set; }
public ICommand MarkAsReadCommand => new RelayCommand(() => this.IsMarkedAsRead = true);
public ICommand MarkAsUnreadCommand => new RelayCommand(() => this.IsMarkedAsRead = false);
private bool isMarkedAsRead;
public bool IsMarkedAsRead
{
get => this.isMarkedAsRead;
set
{
this.isMarkedAsRead = value;
OnPropertyChanged();
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
ListBox
<ListBox ItemsSource="{Binding Notifications}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Background="{TemplateBinding Background}">
<Button x:Name="ContentPresenter"
ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}, Path=ItemTemplate}"
Content="{TemplateBinding Content}"
Command="{Binding MarkAsReadCommand}"
Foreground="Red">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border>
<ContentPresenter />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsMarkedAsRead}" Value="True">
<Setter TargetName="ContentPresenter" Property="Foreground" Value="Green" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type Notification}">
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Thanks a lot #BionicCode for the comprehensive answer. I ended up going with another solution which may or may not be good convention; I am a hobbyist.
Firstly, I don't need databacking / persistence.
Concerning the data model solution and overriding ListBoxItem.Template, I am using a prededfined class 'SyndicationItem' as the data class (my app is Rss Reader). To implement your datamodel solution I guess I could hack an unused SyndicationItem property, or use SyndicationItem inheritance for a custom class (I'm guessing this is the most professional way?)
My complete data model is as follows:
ObservableCollection >>> CollectionViewSource >>> ListBox.
Anyway I ended up using some simple code behind which wasn't so simple at the time:
First the XAML:
<Window.Resources>
<CollectionViewSource x:Key="fooCollectionViewSource" Source="{Binding fooObservableCollection}" >
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="PublishDate" Direction="Descending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<Style x:Key="DeselectedTemplate" TargetType="{x:Type ListBoxItem}">
<Setter Property="Foreground" Value="Gray" />
</Style>
</Window.Resources>
<ListBox x:Name="LB1" ItemsSource="{Binding Source={StaticResource fooCollectionViewSource}}" HorizontalContentAlignment="Stretch" Margin="0,0,0,121" ScrollViewer.HorizontalScrollBarVisibility="Disabled" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="80" />
</Grid.ColumnDefinitions>
<TextBlock MouseDown="TextBlock_MouseDown" Grid.Column="0" Text="{Binding Path=Title.Text}" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Column="1" HorizontalAlignment="Right" TextAlignment="Center" FontSize="11" FontWeight="SemiBold"
Text="{Binding Path=PublishDate.LocalDateTime, StringFormat='{}{0:d MMM, HH:mm}'}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now the code behind:
Solution 1: this applies a new style when listboxitem is deselected. Not used anymore so the LB1_SelectionChanged event is not present in the XAML.
private void LB1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.RemovedItems.Count != 0)
{
foreach (var lbItem in e.RemovedItems)
{
//get reference to source listbox item. This was a pain.
int intDeselectedItem = LB1.Items.IndexOf(lbItem);
ListBoxItem lbi = (ListBoxItem)LB1.ItemContainerGenerator.ContainerFromIndex(intDeselectedItem);
/*apply style. Initially, instead of applying a style, I used mylistboxitem.Foreground = Brushes.Gray to set the text color.
Howver I noticed that if I scrolled the ListBox to the bottom, the text color would revert to the XAML default style in my XAML.
I assume this is because of refreshes / redraws (whichever the correct term). Applying a new style resolved.*/
Style style = this.FindResource("DeselectedTemplate") as Style;
lbi.Style = style;
}
}
}
Solution 2: The one I went with. Occurs on SelectedItem = true, same effect as your first suggestion.
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
TextBlock tb = e.Source as TextBlock;
tb.Foreground = Brushes.Gray;
}

ListView items from an ObservableCollection that need to prepare themselves for realization

I have a ListView which is supposed to display a rather large number of items comprised of a "Name", a "Thumbnail" and a "AnimationPosition" property. A background task in each item's type is in charge of switching the thumbnails in order to animate them.
Now it goes without saying that this is a rather heavy operation and should be limited to as few items as possible e.g. to visible/realizing items of a Virtualized ListView. Now I already have set the DataContext of my ListView to the ObeservableCollection instance and have bound it to the properties of its type. Here's a peek into my XAML code for that.
<TabControl Grid.Row="0" Grid.Column="2">
<TabControl.Resources>
<Style x:Key="MediaItemStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="Margin" Value="5,5,5,5"/>
<Setter Property="Padding" Value="0,0,0,0"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Grid HorizontalAlignment="Left" VerticalAlignment="Top" Height="Auto" >
<Border x:Name="border" BorderBrush="{x:Null}" BorderThickness="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" CornerRadius="2.5"/>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ContentPresenter/>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="custom:MediaContainerListView">
<Setter Property="ItemsSource" Value="{Binding}"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource MediaItemStyle}"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<DockPanel Width="256">
<Image DockPanel.Dock="Top" Height="144" StretchDirection="Both"
Stretch="Fill" Source="{Binding Thumbnail.Source,Mode=OneWay}"/>
<ProgressBar DockPanel.Dock="Top" Height="2"
Minimum="0" Maximum="{Binding Thumbnail.AnimationPosition.Length}"
Value="{Binding Thumbnail.AnimationPosition.Position}"
Visibility="{Binding Thumbnail.AnimationPosition.Visibility}"/>
<TextBlock DockPanel.Dock="Bottom" Height="40"
TextWrapping="Wrap" TextTrimming="CharacterEllipsis"
TextAlignment="Center" Text="{Binding Name}"/>
</DockPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabItem Header="">
<custom:MediaContainerListView x:Name="MediaContainerView"></custom:MediaContainerListView>
</TabItem>
</TabControl>
Basically, I have two methods that start/stop the animation for each individual item.
public async void StartAnimation()
{
if( Count > 1 )
{
Task thumbnailAnimationTask = AnimationTask( AnimationCancellationToken.Token );
await thumbnailAnimationTask;
}
}
public void StopAnimation()
{
AnimationCancellationToken.Cancel();
}
I have two issues here.
The ListView seems to realize all the items rather than only those visible or within the realization range. I suspect my XAML somehow kills the Virtualization and have tried many solutions with no success. Mind you I need my ListView to be scalable to the MainWindow's dimensions and not be of fixed hight and width.
I need to call StartAnimation when an item is about to be realized and StopAnimation when it has left the view.
Even though my ListView is not correctly Virtualized, if my understanding of how ObservableCollections work is correct, it's only the UI representation of the items that are managed by the Virtualization and not the items themselves i.e. calling the StartAnimation/StopAnimation from the Constructor/Destructor of the items don't help much as they are called for every single item at the time of creation anyway.
Is there a neat way to somehow inform each item that they are about to be realized or leave the ListView view?
Update:
The issue of virtualizing not working correctly was related to the WrapPanel and once I switched to VirtualizingStackPanel it started to work correctly. Unfortunately it's not exactly the same as a WrapPanel and since .NET framework does not offer a VirtualizingWrapPanel, I chose to use the one from here. It's not perfect but it does the job.
You said it was 1/2
Look for a call to GetHashcode
I think it calls it GetHashcode just to find it
By accident I discovered it is called when the item is virtualized
Have an animation that terminates (does not loop)
I finally solved the problem myself. I knew it wouldn't have to be so complicated and it really isn't. Since I already had created my own ListView inherited class called MediaContainerListView, I could override a few of its virtual methods. Two of them proved to be just what I wanted.
protected override void PrepareContainerForItemOverride( DependencyObject element, object item )
which is called just before the item is about to appear, and
protected override void ClearContainerForItemOverride( DependencyObject element, Object item )
which is called just before the item is about to disappear. So I call the StartAnimation in the first one and the StopAnimation in the second one and it works flawlessly!

Two selected tabs in tabcontroller

I'm having an issue with a TabControl where I manage (in some special cases) to get two tabs headers selected (only one body showing afaik), and I can't change the selected tab.
Selected tabs have bold header text.
In this image, "Ämnesinformation" and "R43" are both selected.
My application is structured as follows:
I have some views:
MainView: The main view, contains the TabControl which only contains one item in the image.
SubstanceTabsView: One of these for every tab in MainView.
SubstanceView and ClassificationView: the first is used for the "Ämnesinformation", of which there is only one per substance. The second can have multiple instances, like "R43", "R12" etc.
I also have some viewModels:
MainViewModel: The VM for the MainView.
SubstanceTabsViewModel: The VM for the SubstanceTabsView, contains a set of IViewModels
SubstanceViewModel, ClassificationViewModel: both implement IViewModel, are VMs for SubstanceView and ClassificationView
Some relevant xaml code:
Here's the tabcontrol in MainView.xaml
<TabControl SelectedItem="{Binding Path=SelectedTab}" ItemsSource="{Binding Path=Tabs}" >
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Header}" >
</TextBlock>
<local:CrossButton Margin="3" Padding="0" Width="12" Command="{Binding CloseCommand}"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border
Name="Border"
Margin="0,0,-4,0"
Background="{Binding Path=HeaderBackground}"
BorderBrush="#A0A0A0"
BorderThickness="1,1,1,1"
CornerRadius="3,10,0,0" >
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="12,2,12,2"
RecognizesAccessKey="True"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Panel.ZIndex" Value="100" />
<Setter TargetName="Border" Property="Background" Value="{Binding HeaderBackground}" />
<Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Border" Property="Background" Value="Yellow" />
<Setter TargetName="Border" Property="BorderBrush" Value="Black" />
<Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
<Setter Property="Foreground" Value="Green" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate DataType="{x:Type localViewModels:SubstanceTabsViewModel}">
<localViews:SubstanceTabsView />
</DataTemplate>
</TabControl.Resources>
</TabControl>
Here's how I control the connection between different views and viewmodels in SubstanceTabsView.xaml
<TabControl SelectedItem="{Binding Path=SelectedTab}" ItemsSource="{Binding Path=Tabs}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Header}" />
<local:CrossButton Margin="3" Padding="0" Width="12" Command="{Binding CloseCommand}"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<DataTemplate DataType="{x:Type localViewModels:ClassificationViewModel}">
<localViews:ClassificationView />
</DataTemplate>
<DataTemplate DataType="{x:Type localViewModels:SubstanceViewModel}">
<localViews:SubstanceView />
</DataTemplate>
</TabControl.Resources>
</TabControl>
Here's the code for SubstanceTabsViewModel.cs which controls the second level tabs, the setter for the selectedTab controls some logic which asks the user about changing from an unsaved tab:
private IViewModel selectedTab;
public IViewModel SelectedTab
{
get
{
return selectedTab;
}
set
{
MessageBoxResult rsltMessageBox = MessageBoxResult.Yes;
if (selectedTab != null && selectedTab.SaveNeeded() && selectedTab.Id != 0 && value != null && selectedTab is ClassificationViewModel)
{
rsltMessageBox = notifyUserService.Ask("Bedömning är ändrad men ej sparad vill du verkligen lämna fliken?", "Bedömning ändrad");
}
if (rsltMessageBox == MessageBoxResult.Yes)
{
selectedTab = value;
}
OnPropertyChanged("SelectedTab");
}
}
private ObservableCollection<IViewModel> tabs;
public ObservableCollection<IViewModel> Tabs
{
get
{
return tabs;
}
set
{
tabs = value;
OnPropertyChanged("Tabs");
}
}
Some things my investigations have resulted in: If I don't do the notifyUserService call (which results in a messagebox.show()), there is no problem, only one tab is selected. If I look at the SelectedItem of the TabControl, it is only one item, the item it "should" be in my situation.
I finally found someone else having a similar problem, as described here**, "Displaying a message box causes a nested message pump; which means that almost all processing resumes. Of course, we are in the middle of trying to change the selected item, so this can cause all sorts of out-of-order or reentrancy problems. This class of problems is difficult to fix, and we are not going to be able to fix this in our next release." So the problem was with using MessageBox:es in the selectedItem setter.
I guess using some clever workaround is the appropriate solution in this case.
** Update March 2022
The URL referenced by the original post is no longer valid. The content can now be found here: WPF TabControl bug

I need help implementing a style into my radTreeView

I want to take the highlight background off of the radtreeview. I created a style to do this, but I keep getting errors and exceptions such as "Items collection must be empty." If I comment out the style the application works fine, so I know that it is the cause of the problem. I am fairly new to WPF, and I am sure I just don't understand how to use styles yet.
Thanks for your help. Here is the code.
<Grid x:Name="LayoutRoot" Background="Salmon">
<telerik:RadTreeView x:Name="radTreeView" Margin="8" ItemsSource="{Binding Errors}" Background="Salmon" Style="{StaticResource treeStyle}">
<Style TargetType="{x:Type telerik:RadTreeViewItem}" x:Name="treeStyle">
<Setter Property="Focusable" Value="False"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="{x:Null}"/>
<Setter Property="BorderBrush" Value="{x:Null}"/>
</Trigger>
</Style.Triggers>
</Style>
<telerik:RadTreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubItems}" >
<Grid Background="Salmon">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Description}" IsHitTestVisible="False" />
<ListBox Grid.Row="1" ItemsSource="{Binding Messages}" Margin="20,0,0,0" BorderBrush="#00000000" BorderThickness="0" Background="Salmon" IsHitTestVisible="False" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Message}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</HierarchicalDataTemplate>
</telerik:RadTreeView.ItemTemplate>
</telerik:RadTreeView>
</Grid>
</UserControl>
If you know that this is not going to work, I was also trying to get rid of the highlight with the style code:
<Style TargetType="TreeViewItem">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="#FFF"/>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="#000"/>
</Style.Resources>
</Style>
You get exceptions because your style tag is actually an item in the tree, and you have ItemsSource set.
Surround the style with <telerik:RadTreeView.ItemContainerStyle> tag.
This should solve the exception but it will not give you the result that you expect since the control template of the tree view item is actually showing another border that is not affected by the Background property. You will need to change the control template.
Telerik change the styles between releases, so giving you a template of a wrong version will probably won't help you.
But, you can go to the installation folder for Telerik and look for a folder called "Themes". There you'll find a solution with all the themes for telerik.
Choose the one that you use.
Find the resource dictionary for the tree view and copy the style and template for the item to your project.
Change xmlns definitions, make sure you have all the brushes and resources that the style depends upon.
Run to see that the style is ok.
In the template, find the VisualState with x:Name="MouseOver" and delete the storyboard inside it.

Categories