I'm trying to create a trigger to disable the combobox drop down button if there are no items. This is the XAML code I've tried so far, however I am unsure of how to detect whether there are no items contained in the ComboBox, and how to disable the button which drops down the list specifically.
<Style TargetType="ComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<ControlTemplate.Triggers>
<Trigger Property="Items.Count" Value="0">
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This worked for me:
<ComboBox IsEnabled="{Binding RelativeSource=
{RelativeSource Mode=Self}, Path=ItemsSource.Count}"/>
Assuming whatever you have bound to your ItemsSource property has a count method (it worked for ObservableCollection). It's actually kind of interesting that count being 0 resolves to false in xaml, however this wouldn't be the case in C#.
You can add it to a style if you need to add it to a control programatically
<Style TargetType="ComboBox" x:Key="ComboStyle">
<Setter Property="IsEnabled" Value="{Binding RelativeSource=
{RelativeSource Mode=Self}, Path=ItemsSource.Count}"/>
</Style>
ComboBox cbo = new ComboBox();
cbo.ItemsSource = MyData;
cbo.Style = Resources["ComboStyle"] as Style;
Related
How can I get the SelectedItem of a ListBox to be highlighted after setting the SelectedItem in the ViewModel?
The ItemsSource is bound to an ObservableCollection of Bar (the collection is a member of a class Foo. A button is bound to a command that adds a new empty Bar instance to the collection and then also sets SelectedItem to the new empty instance.
After adding the instance to the collection, the ListBox is updated to show the new blank Bar. However, after setting the SelectedItem property in the ViewModel, the new instance is not highlighted in the ListBox but it is being set and the PropertyChanged event is raised (the SelectedItem is displayed elsewhere in the View).
Additional Details:
INotifyPropertyChanged is implemented in a base ViewModel class, and also implemented in the Foo and Bar classes.
The ListBox contains a custom ItemTemplate to display Bar members, and a custom ItemContainerStyle that modifies the Background for the IsMouseOver trigger.
simplified xaml:
<ListBox ItemsSource="{Binding Path=MyFoo.BarCollection}"
SelectedItem="{Binding Path=SelectedItem,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
<Button Content="Add New Bar"
Command="{Binding Path=AddBarCommand}"/>
simplified viewmodel:
private Foo _myFoo;
public Foo MyFoo
{
get { return _myFoo; }
set { _myFoo= value; OnPropertyChanged("MyFoo"); }
}
private Bar _selectedItem;
public Bar SelectedItem
{
get { return _selectedItem; }
set { _selectedItem = value; OnPropertyChanged("SelectedItem"); }
}
private void AddBar()
{
Bar newBar = new Bar();
MyFoo.BarCollection.Add(newBar);
SelectedItem = newBar ;
_unsavedChanges = true;
}
Your code worked perfectly for me.
Notice that "Bar 3" is selected, but when the listbox doesn't have focus, the default theme on Windows 7 works pretty hard to keep you from noticing. And Windows 7 isn't even the worst of the bunch. Our application uses WhiteSmoke as a default background, and we've had major issues with older users1 being unable to tell if listbox items are selected or not.
Fortunately, it's a trivial fix. These resources could just as easily be in Window.Resources, in a global ListBox style, or in App.xaml. It's up to you how widely you want to apply them.
<ListBox
ItemsSource="{Binding MyFoo.BarCollection}"
SelectedItem="{Binding SelectedItem}"
>
<ListBox.Resources>
<SolidColorBrush
x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}"
Color="{x:Static SystemColors.HighlightColor}"
Opacity="0.5"
/>
<SolidColorBrush
x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}"
Color="{x:Static SystemColors.HighlightTextColor}"
/>
</ListBox.Resources>
</ListBox>
1 "Older" meaning old enough to vote.
And if your version of .NET predates the existence of SystemColors.InactiveSelectionHighlightTextBrushKey, this could probably use some refinement, but it works:
<ListBox
>
<ListBox.ItemContainerStyle>
<Style
TargetType="ListBoxItem"
BasedOn="{StaticResource {x:Type ListBoxItem}}"
>
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid
Background="{TemplateBinding Background}"
>
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter
Property="Background"
Value="{StaticResource {x:Static SystemColors.HighlightBrushKey}}"
/>
<Setter
Property="Foreground"
Value="{StaticResource {x:Static SystemColors.HighlightTextBrushKey}}"
/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True" />
<Condition Property="IsEnabled" Value="False" />
</MultiTrigger.Conditions>
<Setter
Property="Background"
Value="{StaticResource {x:Static SystemColors.InactiveCaptionBrushKey}}"
/>
</MultiTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
I prepared my own DataTemplate to display my own Item class objets. To set the width I use binding to ViewportWidth of ScrollViewer. This is how:
<DataTemplate x:Key="MyItemTemplate">
<Grid Margin="5" HorizontalAlignment="Stretch"
Width="{Binding ViewportWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ScrollViewer}}}">
...
</Grid>
</DataTemplate>
How I display data:
<TabItem Header="Shop">
<!--<ScrollViewer>-->
<ListView Name="ShopListView" ItemsSource="{Binding itemList}"
ItemTemplate="{StaticResource MyItemTemplate}"
ItemContainerStyle="{StaticResource alternatingListViewItemStyle}"
AlternationCount="2"
ScrollViewer.CanContentScroll="True"
/>
<!--</ScrollViewer>-->
</TabItem>
The issue is the grid that makes my DataTemplate is sliglty broader - it overlaps the right border of the View Window.
EDIT
Buttons on their right edges are cut.
<Style x:Key="alternatingListViewItemStyle" TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<!-- setting up triggers for alternate background colors -->
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="#FF4C85FF"></Setter>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="2">
<Setter Property="Background" Value="#FFFF8C7C"></Setter>
</Trigger>
</Style.Triggers>
</Style>
The default style of the item container element generated by the ListView for each item incorporates some padding. That means, rendered items normally have a width which is slightly smaller than the view port width.
To not let your items cross the right border of the viewport, you could set the padding of the item container to zero. This can easily be done with a simple style for the item container. Since you already provide an item container style, you could just let it set the padding property.
However, this might not be necessary. To stretch the item content across the width of the view port, you don't need to bind the Grid.Width property in your item template to ListView.ViewPortWidth. It can be done simpler: The item container can be instructed to horizontally stretch the item content by setting its HorizontalContentAlignment accordingly. This of course can also be done with the item container style.
The below example style (based on the style given in your question) demonstrates both setting the HorizontalContentAlignment and Padding property. If you want to keep the default padding between the items and the border of the ListView, simply omit the Padding setter from my example:
<Style x:Key="alternatingListViewItemStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Padding" Value="0" />
<Style.Triggers>
<!-- setting up triggers for alternate background colors -->
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="#FF4C85FF"></Setter>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="2">
<Setter Property="Background" Value="#FFFF8C7C"></Setter>
</Trigger>
</Style.Triggers>
</Style>
I am a newbie in WPF, I have always done validation for various UI controls using custom ValidationRule classes, however, when using DataGrid for the first time and binding it with a simple DataTable, I found that the DataGrid has a pretty good default validation that detects the type of DataTable columns and gives a visual error if a cell value is not of the same expected type. This is pretty enough for me that I thought no need to create custom validation rules as the default one is fitting my purpose. However, I have a Submit button that I need to disable if this DataGrid has any errors, so I thought that this would be easy utilizing the Validation.HasError property using the following code:
<Button x:Name="btnSubmit" Content="Submit">
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=(Validation.HasError),ElementName=dataGrid}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
But unfortunately, it seems that Validation.HasError is always False whatever the value I enter in the datagrid cells in Runtime. The default visual validation is working properly, the cell gets a red border when an incorrect value is entered, however, no notification is sent that there is an error coming from the dataGrid.
Is there any way to detect within XAML that the default visual validation of the dataGrid is producing an error? or do I have to use a custom validation rule for this purpose?
You can create a global validation on your Ap.xaml file. So Your control will have a red asterisk and the error message as Tooltip . Any control of your project can use the same validation.
In App.xaml file:
<Style x:Key="AsteriskTemplate" TargetType="Control">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Red"
FontSize="14pt"
Margin="-15,0,0,0" FontWeight="Bold">*
</TextBlock>
<Border>
<AdornedElementPlaceholder Name="myControl"/>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="DataGrid" BasedOn="{StaticResource AsteriskTemplate}" />
I'm new to the styling part of WPF. What I want to do is to get the value of an attached property in a setter, e.g.:
<Trigger Property="SomeProperty" Value="SomeValue">
<Setter Property="SomeProperty"
Value="(My attached property, let's say lcl:MyClass.MyString)"/>
</Trigger>
I know that you can get something to this effect using a {TemplateBinding lcl:MyClass.MyString} in a ControlTemplate. My question is: can you do this in a style, without using a ControlTemplate?
You can try to use:
<Setter Property="SomeProperty" Value="{Binding Path=(lcl:MyClass.MyString), RelativeSource={RelativeSource self}}"/>
if your attached property applies to the element as your style. If not, you can use RelativeSource or ElementName to find the appropriate element.
I am not sure how you have done that since your code lacks of details. Below code works:
<UserControl.Resources>
<Style x:Key="LabelStyle" TargetType="{x:Type Label}">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Content"
Value="{Binding Path=(TestWebBrowser:AttachP.ValueEditorState), RelativeSource={RelativeSource self}}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<StackPanel>
<Label x:Name="label" TestWebBrowser:AttachP.ValueEditorState="HelloWorld" Style="{StaticResource LabelStyle}"/>
<Button Content="Disable Label" Click="Button_Click"/>
</StackPanel>
Button's click event handler will set Label's IsEnabled to false to trigger the trigger. And note that you have to use Path= with parenthesis in the binding.
I have a list box whose selection colour is default plain Solid Blue colour. I read this article "How To Change WPF ListBox SelectedItem Color?" here. I want to create style given in it to code behind. so that i can assign this style to my Listbox ItemContainerStyle property.
like
Style s = ......
MyListBox.ItemContainerStyle = s;
I want to make this in code behind because if user changes theme of my software then this style (Selection Colours) should recreate itself to match the changed theme colours.
<Style x:Key="SimpleListBoxItem" TargetType="ListBoxItem">
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border" Padding="2" SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Border" Property="Background" Value="{StaticResource AuthorGradient}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I think you don't have a code behind version of this code, you just have to apply you're existing template to your listbox like below.
if your target is a template.
(NameOfListBox.SelectedItem as ListBoxItem).ContentTemplate = this.Resources["NameOfTemplate"] as DataTempate;
(NameOfListBox.SelectedItem as ListBoxItem).UpdateLayout();
if your target is a style.
(NameOfListBox.SelectedItem as ListBoxItem).Style= this.Resources["NameOfStyle"] as DataTempate;
(NameOfListBox.SelectedItem as ListBoxItem).UpdateLayout();
example
(lstMetaDataCards.SelectedItem as ListBoxItem).ContentTemplate = this.Resources["MetaDataCardAtEditState"] as DataTemplate;
(lstMetaDataCards.SelectedItem as ListBoxItem).UpdateLayout();