XAML ContentControl not changing visibility - c#

I have a little problem with my code. I have a content control that I want to switch the visibility on, based on a value of a property.
I have a toggle button that changes the value of the property IsListView and the icon without a problem.
<ToggleButton Width="26" Height="26" VerticalAlignment="Center" IsChecked="{Binding IsListView}" Command="{Binding SetItemsViewStyle}" Margin="0,0,5,0">
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource MahApps.Styles.Button.Circle}">
<Setter Property="Content" Value="{iconPacks:BootstrapIcons List, Width=12, Height=12}" />
<Setter Property="ToolTip" Value="Switch to list display"/>
<Style.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter Property="Content" Value="{iconPacks:Material Apps, Width=12, Height=12}" />
<Setter Property="ToolTip" Value="Switch to tile display"/>
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
Then I have a style for the content control that I would like to hide or display depending on the value of the IsListView property:
<Style x:Key="ListViewStyle" TargetType="ContentControl">
<Setter Property="Visibility" Value="Collapsed"/>
<Setter Property="Content" Value="{x:Null}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsListView}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="Content">
<Setter.Value>
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalAlignment="Stretch" VerticalScrollBarVisibility="Auto" Margin="0,31,0,0" >
<DataGrid ItemsSource="{Binding Projects}"/>
</ScrollViewer>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
The ContentControl is pretty much empty:
<ContentControl Style="{StaticResource ListViewStyle}">
</ContentControl>
The contents are displayed correctly when the screen is initialized (the IsListView is initialized to true), but the ContentControl does not dissapear when I click the toggle button. The code behind is executed, including fetching data from the data store, but the screen is not refreshed it seems to me.
What am I missing?

Ok, I found out what was the problem. I should have mentioned in my question that I am using ReactiveUI.
I had a property IsListView that was defined like this:
public bool IsListView { get; set; }
It has to be defined like this to ensure that the events are propagated to the view.
private bool _isListView;
public bool IsListView
{
get { return _isListView; }
set { this.RaiseAndSetIfChanged(ref _isListView, value); }
}
Now the controls hide and show as per my requirement.

Related

Changing the style of a border inside data template for the listbox item based on the alterationindex [duplicate]

I have seen some articles that show how to use AlternationIndex with ListBoxes or ListViews, but I had spent a few hours trying to get alternating background colors on the base ItemsControl class and nothing seems to work. All ListBox samples I saw use ListBoxItem as the target type for the style that sets the background based onAlternationIndex - like this one from MSDN:
<Grid>
<Grid.Resources>
<Style x:Key="alternatingWithTriggers" TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="Blue"/>
<Setter Property="Foreground" Value="White"/>
<Style.Triggers>
<Trigger Property="ListBox.AlternationIndex" Value="1">
<Setter Property="Background" Value="CornflowerBlue"/>
<Setter Property="Foreground" Value="Black"/>
</Trigger>
<Trigger Property="ListBox.AlternationIndex" Value="2">
<Setter Property="Background" Value="LightBlue"/>
<Setter Property="Foreground" Value="Navy"/>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<ListBox AlternationCount="3" ItemsSource="{StaticResource data}"
ItemContainerStyle="{StaticResource alternatingWithTriggers}">
</ListBox>
</Grid>
I want to use the ItemsControl because I do not want the selection functionality and I think restyling a ListBox to hide it might not be the best choice.
This is one of the things I was trying:
<DataTemplate DataType="{x:Type vm:ObservableCollectionItem}">
<Grid>
<!-- some content here -->
</Grid>
</DataTemplate>
<!-- ... -->
<ItemsControl
ItemsSource="{Binding ObservableCollectionItems}"
AlternationCount="2"
>
<ItemsControl.ItemContainerStyle>
<Style>
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Grid.Background" Value="Red"></Setter>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Grid.Background" Value="Blue"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
The problem I saw was that the visual tree has a list of ContentPresenters that have ItemsControl.AlternationIndex alternate between 0 and 1, but the Grid in each ContentPresenter has ItemsControl.AlternationIndex set to 0.
There is probably something obvious I am missing...
The ItemContainerStyle is applied to the elements generated by the ItemsControl: ContentPresenter. The ContentPresenter will in turn contain whatever you put in your ItemTemplate. In the case of a ListBox, the ItemContainerStyle is applied to the generated ListBoxItem.
The AlternationCount is, based on what you posted, only available on these generated items. You cannot use the ItemContainerStyle to set the Grid's background, because the Grid is unknown to that Style.
The following would be ideal, but unfortunately ContentPresenter has no background property. It would work for a ListBox (with ListBoxItems) however.
<ItemsControl
ItemsSource="{Binding ObservableCollectionItems}"
AlternationCount="2">
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="Red"></Setter>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="Blue"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
So you end up writing a style for the grid which binds to the AlternationIndex of your parent ContentPresenter.
<DataTemplate DataType="{x:Type vm:ObservableCollectionItem}">
<Grid>
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="0">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="1">
<Setter Property="Background" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
</DataTemplate>
hm... After about 2 hours of playing around, I finally found the solution that simply works:
<ItemsControl ItemsSource="{Binding}" AlternationCount="2">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Background="Transparent" x:Name="__PART_GRID"></Grid>
<DataTemplate.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter TargetName="__PART_GRID" Property="Background" Value="Red"/>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter TargetName="__PART_GRID" Property="Background" Value="Blue"/>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I hope this answer helps others to save some time.
Or, as I found on another post, and it works great for me...
You can simply use a binding...
{Binding
RelativeSource={RelativeSource Mode=TemplatedParent},
Path=(ItemsControl.AlternationIndex)}
NB: remember to add AlterationCount="100" on your ItemsControl
I don't know how any of the prior answers are legit. I couldn't make any of them work (didn't try Jacobi's though). Anyways, I found the path to enlightenment here: http://www.dotnetcurry.com/wpf/1211/wpf-items-control-advanced-topic, which lead me to adding the following in the xaml.cs code-behind:
public sealed class CustomItemsControl : ItemsControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new ContentControl();
}
}
and this in the xaml itself
<local:CustomItemsControl AlternationCount="2"
ItemsSource="{Binding Cells, Mode=OneWay}">
<local:CustomItemsControl.ItemContainerStyle>
<Style TargetType="ContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<Border Background="{TemplateBinding Background}">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="WhiteSmoke"/>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="LightGray"/>
</Trigger>
</Style.Triggers>
</Style>
</local:CustomItemsControl.ItemContainerStyle>
</local:CustomItemsControl>
This was so damn hard to find a working solution to that I'm actually angry
If you don't want to use the DataTemplate approach, you can create a custom control that uses a ContentControl as the item container, therefore allowing you to specify a background color.
Class:
public class ItemsControlAlternating : ItemsControl
{
static ItemsControlAlternating()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ItemsControlAlternating),
new FrameworkPropertyMetadata(typeof(ItemsControlAlternating)));
}
protected override DependencyObject GetContainerForItemOverride()
{
return new ContentControl();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is ContentControl;
}
}
Resource Dictionary:
<Style TargetType="{x:Type c:ItemsControlAlternating}">
<Setter Property="AlternationCount" Value="2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:ItemsControlAlternating}">
<ItemsPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="Gray"/>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</Setter.Value>
</Setter>
</Style>

WPF Automation of UIElements

I'm working on implementing a joystick into my WPF app. I would like to simulate clicking on buttons. I can run the events with the code below, but I want to also show the button being clicked in the UI. Is this possible for button and other UI items?
var invokeProv = (IInvokeProvider) (new ButtonAutomationPeer(ButtonHome).GetPattern(PatternInterface.Invoke));
invokeProv?.Invoke();
ButtonHome.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));
I ended up with this as a possible solution. In the mainwindow class I put...
public static readonly DependencyProperty ButtonPressedProperty =
DependencyProperty.Register("ButtonPressed", typeof(bool), typeof(MainWindow), new PropertyMetadata(null));
added a property...
private bool ButtonPressed
{
get => (bool) GetValue(ButtonPressedProperty);
set => SetValue(ButtonPressedProperty, value);
}
In the mainwindow xaml under the button I put ...
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=(windows:MainWindow.ButtonPressed)}" Value="True">
<Setter Property="Background" Value="{StaticResource PressedButtonBackground}"/>
<Setter Property="Foreground" Value="{StaticResource ButtonForeground}"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=(windows:MainWindow.ButtonPressed)}" Value="False">
<Setter Property="Background" Value="{StaticResource ButtonBackground}"/>
<Setter Property="Foreground" Value="{StaticResource ButtonForeground}"/>
</DataTrigger>
</Style.Triggers>
</Style>
Evaluating my joystick buttons i just used ButtonPressed = gamepadButtons[3];
the only thing that comes to my mind is if I was on your place I'd simply create data trigger on the xaml side as follows:
<Button x:Name="btn" Height="30" Margin="0,5,0,0">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Green" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ButtonPressed, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" Value="True">
<Setter TargetName="bd" Property="Background" Value="Purple" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
then I'd create a property:
private bool _buttonPressed;
public bool ButtonPressed
{
get
{
return _buttonPressed;
}
set
{
if (_buttonPressed != value)
{
_buttonPressed = value;
OnPropertyChanged(nameof(ButtonPressed));
}
}
}
and in the place that you're about to raise that event I'd simply change our boolean value state
var invokeProv = (IInvokeProvider)(new ButtonAutomationPeer(ButtonHome).GetPattern(PatternInterface.Invoke));
invokeProv?.Invoke();
ButtonHome.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));
ButtonPressed = true;
The things that you have to remember about within this solution:
Implement INotifyPropertyChanged interface,
Make sure that your button template can reach the datacontext, where our property is created, for example I've setup RelativeSource to the Window ancestor type which isn't really nessecary i think. You might do it better and faster.
If there's a better solution for raising visual state of UI elements I'd like to know about it! Kind Regards.

Gridsplitter visibile on MouseOver

I want a GridSplitter to be visible only when the user has the mouse over it. For that I'm setting a DataTrigger in its style. But I can't figure out what it's wrong because I don't get the desired behavior. It just stays the same.
<GridSplitter
ResizeDirection="Columns"
ResizeBehavior="BasedOnAlignment"
Grid.Column="1"
Grid.Row="0"
Grid.RowSpan="2"
Width="8"
Height="Auto"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Background="AliceBlue"
Margin="-3 0 0 0">
<GridSplitter.Style>
<Style TargetType="{x:Type GridSplitter}">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseOver}">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</GridSplitter.Style>
</GridSplitter>
Do you see something wrong? Does GridSplitter must be styled in a different way?
I figured it out, Triggers must be used instead of DataTriggers:
<GridSplitter.Style>
<Style TargetType="{x:Type GridSplitter}">
<Setter Property="Background" Value="Transparent"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="AliceBlue"/>
</Trigger>
</Style.Triggers>
</Style>
</GridSplitter.Style>
By the way, I think that if I set the Visibility to Hidden I can't target the GridSplitter, so I switch its Background instead.

DataTemplate check if Binding exist

I had make an Button Style as DataTemplate in a ResourceDictionary. Here a small part:
<Style TargetType="{x:Type Button}">
<Setter Property="Focusable" Value="False"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border">
...
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" TargetName="border" Value="Red" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In this Template a have a binding to a Propertie IsSelected. This Propertie is in one situation there and in a other not.
Is it possible to Check in Xaml if the binding path exist, then use it in other case forget it?
Now i had BindingExpression in the Debug output and i want to eliminate this.
The more pertinent question is: why do you have a DataTrigger in your ControlTemplate? This creates a dependency between the control and its data context, which is why you're experiencing this issue when your data context does not match the control template's expectations.
Are you certain you cannot use a more suitable mechanism? For example, could you instead use a style for those buttons where IsSelected should affect the Background?
<Style x:Key="SpecialButtonStyle" TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
...
<Button DataContext="relevant data context" Style="{StaticResource SpecialButtonStyle}"/>
Or, even better, could you define a data template for the specific data class you have that has the IsSelected property? This data template could automatically use the correct Button style.

How do you highlight an item in a combobox using WPF?

I have a combobox filled with a list of Objects. I like to highlight an item in a combobox based on a IsHighlighted property of the Object.
I've tried writing my own style but no real success...
<Style x:Key="SimpleComboBoxItem" TargetType="ComboBoxItem">
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Border Name="Border" Padding="2" SnapsToDevicePixels="true">
<ContentPresenter x:Name="contentPresenter" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Border" Property="Background" Value="#FFCCCCCC"/>
</Trigger>
<Trigger Property="Tag" Value="Highlight" SourceName="contentPresenter">
<Setter Property="Background" TargetName="Border" Value="#FFAAF3A0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Thanx in advance
This should work fine with a simple DataTrigger.
Your Object Class:
public class TestObject
{
public string Name { get; set; }
public bool IsHighlighted { get; set; }
public override string ToString()
{
return this.Name;
}
}
Xaml:
<Window x:Class="TestWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWPF"
Title="MainWindow">
<Grid>
<StackPanel>
<ComboBox>
<ComboBox.Resources>
<Style TargetType="ComboBoxItem">
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Background" Value="Gray" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsHighlighted}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Resources>
<local:Employee Name="Nick" />
<local:Employee Name="Bob" IsHighlighted="True" />
<local:Employee Name="Fred" />
</ComboBox>
</StackPanel>
</Grid>
</Window>
Note: Unlike the sample above, I'm guessing in your code you're binding the combobox's ItemsSource... which should work just the same. One thing to be careful of though, is if your object's 'IsHighlighted' property can change, you should be implementing INotifyProperty changed to ensure that changing the value will notify the UI that the triggers should refresh.
You might wanna re-define the HighlightBrushKey, overriding default highlight style:
<ComboBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="FFAAF3A0" />
</ComboBox.Resources>
Should do the trick for you.
(if it is more general, put it directly in the UserControl.Resources / Window.Resources )

Categories

Resources