WPF Combo Box Style - wpf

I have two combo boxes: CarTypeComboBox and SeriesComboBox.
Issues:
1. I want the SeriesCombox to be visible only when the user select BMW.
2. System.Windows.Style is showing up in SeriesComboBox.
Thank you
Complete Code:
<Window x:Class="StyleTrigger.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:StyleTrigger"
xmlns:local2="clr-namespace:ComboBoxData"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources >
<local2:ComboBoxItemCollection x:Key="CarItemsCollection"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*" />
<ColumnDefinition Width="50*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Grid.Column="0" >
<Label x:Name="CarBrand" Height="30" Width="75" Margin="10,0,0,0" Content="Car Brand"
HorizontalAlignment="Left" VerticalAlignment="Top"/>
<ComboBox x:Name="CarTypeComboBox" Margin="10,0,0,0" Width="100" Height="30" HorizontalAlignment="Left" VerticalAlignment="Top"
ItemsSource="{Binding Mode=OneWay, Source={StaticResource CarItemsCollection}}"
DisplayMemberPath="CarType"
SelectedValuePath="CarID"
/>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" >
<Label x:Name="CarSeries" Height="30" Width="75" Margin="10,0,0,0" Content="Car Series"
HorizontalAlignment="Left" VerticalAlignment="Top"/>
<ComboBox x:Name="SeriesComboBox" Margin="10,0,0,0" Width="100" Height="30"
HorizontalAlignment="Left" VerticalAlignment="Top">
<sys:String>230</sys:String>
<sys:String>280</sys:String>
<sys:String>530</sys:String>
<Style TargetType="ComboBox">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem.CarType, ElementName=CarTypeComboBox}" Value="BMW">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox>
</StackPanel>
</Grid>
</Window>
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ComboBoxData
{
class SingleComboBoxItem
{
public SingleComboBoxItem(int pCarID,String pCarBrand)
{
CarID = pCarID;
CarType = pCarBrand;
}
public string CarType { get; set; }
public int CarID { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ComboBoxData
{
class ComboBoxItemCollection : ObservableCollection<SingleComboBoxItem>
{
public ComboBoxItemCollection() : base()
{
Add(new SingleComboBoxItem(1,"Honda"));
Add(new SingleComboBoxItem(2,"Toyota"));
Add(new SingleComboBoxItem(3,"BMW"));
Add(new SingleComboBoxItem(4,"Dodge"));
Add(new SingleComboBoxItem(5,"Lexus"));
}
}
}

Add an additional Setter with the default value Hidden to the Style itself, not the triggers.

Your ComboBox style should look like this:
<Style TargetType="ComboBox">
<!-- Just add this one Setter -->
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem.CarType, ElementName=CarTypeComboBox}" Value="BMW">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
Do not set the Visibility attribute on the ComboBox tag itself. No Visibility="Hidden" on the ComboBox. That will override the style setters and it will never be visible. ONLY set Visibility in setters in the style.
UPDATE
Now that I've seen the whole code, I can offer a little more insight. First, you said "The SeriesComboBox is not appearing when I select BMW.", but what's happening in the version you just posted is that it is not disappearing when you don't select BMW. Now, let's take a look at what it does do:
There's an anomaly in that dropdown list: The last item is System.Windows.Style. I'm willing to bet you haven't ever seen that model of BMW on the road any more than I have.
Your Style is correctly defined, and I think it may have been correct even before we started hassling you about it. The trouble is you aren't assigning it to the Style property of the ComboBox. Instead you're adding it to the default content property, which in the case of ComboBox is Items. In WPF, you can throw literally almost figuratively just about anything at all in a ComboBox (or ListBox) items collection. It's a collection of object. It'll eat any old garbage you feed it and never complain. Since you didn't specify DisplayMemberPath for that one, it just cheerfully calls ToString() on each object in turn.
So to assign the style to the Style property of the ComboBox, put it inside <ComboBox.Style>:
<ComboBox
x:Name="SeriesComboBox"
Margin="10,0,0,0"
Width="100"
Height="30"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<sys:String>230</sys:String>
<sys:String>280</sys:String>
<sys:String>530</sys:String>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger
Binding="{Binding SelectedItem.CarType, ElementName=CarTypeComboBox}"
Value="BMW"
>
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
You could also define the Style in Resources, give it x:Key="SeriesComboBoxStyle", and set the Style attribute on the ComboBox tag: Style="{StaticResource SeriesComboBoxStyle}".

Related

WPF XAML Local (child) styles are being overwritten. Why?

I wrote a simple dialog (XAML/WPF) and a test app and the dialog looks fine. In particular the buttons in the ListView have rounded corners. I've posted a picture and the code below.
The problem? When I use this dialog inside a much larger program (codebase too large to share), the rounded corners and other styling is gone. I strongly suspect something in the larger program is taking precedence over my local work. Perhaps a global style for buttons or some such thing?
I'd like to understand what is going on. Presumably something in the main app takes precedence over my xaml work?
I'd like to know if there is a way to say "don't inherit styles from the app itself. Rather use WPF defaults unless I override them.", assuming that is the problem.
See picture (notice rounded corners)
See picture from when I call it from actual main application instead of test application
Notice in particular lack of rounded corners. My work to produce rounded corners is gone! Also, in the test app, hovering over a button shows a blue color which I assume is default (I didn't do it). When called from main app, no such hover effect. I suspect the main app gets rid of that somewhere.
Here's the simple dialog xaml
<Window x:Class="FirmsDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Dialogs"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="400"
Width="390" Height="720" BorderBrush="LightGray"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
x:Name="FirmsViewDlg" Loaded="FirmsViewDlg_Loaded"
>
<Window.Resources>
<FontFamily x:Key="AvenirNextforCompany">
pack://application:,,,/Assets/Fonts/#AvenirNextforCompany
</FontFamily>
<local:ReverseObjectToBool x:Key="ReverseObjectToBoolConverter" />
<local:ObjectToBool x:Key="ObjectToBoolConverter" />
<!-- New style -->
<Style x:Key="StyleListViewItem" TargetType="ListViewItem">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Green"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Blue"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- Existing style -->
<Style x:Key="StyleListView" TargetType="ListView">
<Setter Property="ItemContainerStyle" Value="{StaticResource StyleListViewItem}"/>
<Setter Property="SnapsToDevicePixels" Value="true"/>
<!-- ... -->
</Style>
</Window.Resources>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Content="Select Company" HorizontalAlignment="Center" FontSize="20px" Foreground="#393a3d" FontFamily="{StaticResource AvenirNextforCompany}" FontWeight="Normal" ></Label>
<Label Grid.Row="1" Content="Accountant companies" Margin="10,0" FontFamily="{StaticResource AvenirNextforCompany}" FontSize="14px" Foreground="#8d9096" FontWeight="Normal"></Label>
<ListView BorderThickness="0" Grid.Row="2" ItemsSource="{Binding RealmMembershipInfo}" SelectedItem="{Binding SelectedFirm}" x:Name="realmListBox"
HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch" Height="Auto" Margin="0,0,0,0"
VerticalAlignment="Top" FontWeight="Bold" FontSize="14" Background="White"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
BorderBrush="LightGray">
<ListView.ItemTemplate>
<DataTemplate>
<Button Click="Button_Click"
MinHeight="65" Padding="10,0,10,0"
Margin="0,0,0,0" HorizontalContentAlignment="Left" HorizontalAlignment="Stretch" BorderBrush="LightGray" Background="White" Foreground="#393a3d" FontFamily="{StaticResource AvenirNextforCompany}" FontSize="14px" FontWeight="SemiBold"
>
<Button.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="9"/>
</Style>
</Button.Resources>
<TextBlock Text="{Binding displayName}" TextWrapping="Wrap" HorizontalAlignment="Stretch" Foreground="#393a3d" FontFamily="{StaticResource AvenirNextforCompany}" FontSize="14px" FontWeight="Bold" >
</TextBlock>
</Button>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Margin" Value="10,10,10,10"/>
<Setter Property="Padding" Value="0"/>
<Style.Triggers>
<Trigger Property="Control.IsMouseOver" Value="True">
<Setter Property="Control.Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
</Window>
Simple program to show the Xaml above
using Dialogs;
using Models;
using System.Collections.Generic;
using System.Windows;
namespace TestApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_SelectFirm(object sender, RoutedEventArgs e)
{
// hack code to set up dialog
RealmMembershipInfo realmInfo = new RealmMembershipInfo();
realmInfo.realmMembershipInfo = new List<RealmMembershipItem>();
RealmMembershipItem item = new RealmMembershipItem();
item.displayName = "Company 1";
realmInfo.realmMembershipInfo.Add(item);
item = new RealmMembershipItem();
item.displayName = "Company2";
realmInfo.realmMembershipInfo.Add(item);
FirmsDialog dlg = new FirmsDialog(realmInfo);
dlg.ShowDialog();
MessageBox.Show("Your picked firm: " + dlg.SelectedFirm);
}
private void Button_SelectClient(object sender, RoutedEventArgs e)
{
}
}
}
You haven't set the style on the Listview.
<ListView Style={StaticResource StyleListView} ....
And you are using the default style for the ItemsContainer. You need to add the a base style.
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem" BasedOn={StaticResource StyleListViewItem}...

WPF DataTriggers on ContentControls are firing after their bound Dependency Property's PropertyChangedCallback

I'm coding a WPF input dialog window, that will show a different Control based on a dependency property named InputType.
The language I'm using is Visual COBOL .NET, but the issue is not related to the language but to WPF itself, and the language is easily understandable by VB and C# programmers.
This is the XAML code for my dialog window
<Window x:Name="wndDialog"
x:Class="ClassLibraryNew.AGInputBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lib="clr-namespace:ClassLibraryNew"
xmlns:ctrl="clr-namespace:ClassLibraryNew.Controls"
Width="400"
MinHeight="200"
WindowStyle="None"
WindowStartupLocation="CenterOwner"
ResizeMode="NoResize"
Background="#FFEEEEEE"
SizeToContent="Height"
MouseDown="OnMouseDown"
Loaded="OnLoaded">
<Window.CommandBindings>
<CommandBinding Command="{x:Static lib:DialogCommands.OkCommand}" CanExecute="OnCommandCanExecute" Executed="OnCommandExecuted"/>
<CommandBinding Command="{x:Static lib:DialogCommands.CancelCommand}" CanExecute="OnCommandCanExecute" Executed="OnCommandExecuted"/>
</Window.CommandBindings>
<Border Style="{StaticResource AGTWindowBorder}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox Grid.Row="0" Header="{Binding Caption}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="{Binding Text}"
FontSize="{Binding Converter={StaticResource FontSizeConverter}, ConverterParameter='16'}"
VerticalAlignment="Center"
TextWrapping="Wrap"/>
<ContentControl Grid.Row="1" x:Name="contentControl" >
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Foreground="Red" Text="Errore: input type non valido. Contattare l'assistenza tecnica."/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding InputType}" Value="Text">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ctrl:TextField Text="{Binding Value, ElementName=wndDialog}"
ctrl:WatermarkService.Watermark="{Binding WatermarkText, ElementName=wndDialog}"
ctrl:WatermarkService.HideWhenFocused="False"
MaxLength="{Binding MaxLength, ElementName=wndDialog}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding InputType}" Value="Integer">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ctrl:IntegerField Value="{Binding Value, ElementName=wndDialog}"
ZeroFill="{Binding ZeroFill, ElementName=wndDialog}"
MaxLength="{Binding MaxLength, ElementName=wndDialog}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding InputType}" Value="Decimal">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ctrl:DecimalField Value="{Binding Value, ElementName=wndDialog}"
DecimalDigits="{Binding DecimalDigits, ElementName=wndDialog}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</GroupBox>
<StackPanel Grid.Row="1"
Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="8">
<StackPanel.Resources>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource EuroButton}">
<Setter Property="Width" Value="80"/>
<Setter Property="Margin" Value="8,0,0,0"/>
</Style>
</StackPanel.Resources>
<Button IsDefault="True"
Command="{x:Static lib:DialogCommands.OkCommand}">
<AccessText Text="_Ok"/>
</Button>
<Button IsCancel="True"
Command="{x:Static lib:DialogCommands.CancelCommand}">
<AccessText Text="_Annulla"/>
</Button>
</StackPanel>
</Grid>
</Border>
What I want to achieve is showing the user a 'TextField' (custom TextBox) when the InputType is Text, 'IntegerField' when the InputType is Integer, etc..
InputType's type is an enum name DialogInputType which contains three values (Text, Integer, Decimal).
This works fine, however I need a way to to attach an event handler to the Field inside the ContentControl when its content has been correctly set and is not null.
I expected the DataTriggers to re-evaluate when the InputType changed, instead this fails:
(Visual COBOL .NET)
01 InputTypeProperty type DependencyProperty public static initialize only
value type DependencyProperty::Register(
"InputType",
type of DialogInputType,
type of AGInputBox,
new FrameworkPropertyMetadata(
type DialogInputType::Text,
new PropertyChangedCallback(method OnInputTypeChanged)
)
).
*> property definition omitted...
method-id OnInputTypeChanged private static.
procedure division using by value sender as type DependencyObject, by value e as type DependencyPropertyChangedEventArgs.
if sender not instance of type AGInputBox
goback
end-if
declare wnd as type AGInputBox = sender as type AGInputBox
if wnd::contentControl::Content instance of type FieldBase *> debugger arrives here
declare textField as type FieldBase
set textField = wnd::contentControl::Content as type FieldBase
attach method wnd::OnFieldTemplateApplied to textField::TemplateApplied
end-if
end method.
The VS debugger shows that the ContentControl's Content is null, but then the window is correctly visualized, and maybe its content is set later...
It is also null in:
- Loaded Event of the Window
- ContentRendered Event of the Window
And I can't set a Loaded RoutedEventHandler inside the DataTemplate Control, neither with Loaded="OnFieldLoaded" nor with Style + EventSetter, because it's forbidden and won't compile (even if the compiler error suggests to use the EventSetter :/).
Edit: I tried l33t's solution but unfortunately OnContentChanges is never getting called, even if the content is correctly set.
I created this class:
class-id ClassLibraryNew.Controls.NotifyingContentControl public
inherits type ContentControl.
01 ContentChanged type EventHandler event public.
method-id new public.
procedure division.
invoke super::new()
end method.
method-id OnContentChanged protected override.
procedure division using by value oldContent as object, by value newContent as object.
invoke super::OnContentChanged(oldContent, newContent) *> I put a debugger breakpoint here but it's not getting hit
invoke RaiseContentChanged()
end method.
method-id RaiseContentChanged private.
procedure division.
declare handler as type EventHandler = ContentChanged
declare e as type EventArgs = new EventArgs()
if handler not = null
invoke run handler(by value self, e)
end-if
end method.
end class.
Define the DataTemplates as resources and handle the Loaded event of the TextField, IntegerField and DecimalField root elements:
<ContentControl Grid.Row="1" x:Name="contentControl" >
<ContentControl.Resources>
<DataTemplate x:Key="tfTemplate">
<ctrl:TextField ... Loaded="LoadedHandler"/>
</DataTemplate>
<!-- + DataTemplates for IntegerField and DecimalField -->
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Foreground="Red" Text="Errore: input type non valido. Contattare l'assistenza tecnica."/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding InputType}" Value="Text">
<Setter Property="ContentTemplate" Value="{StaticResource tfTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding InputType}" Value="Integer">
<Setter Property="ContentTemplate" Value="{StaticResource ifTemplate}" />
</DataTrigger>
...
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Even if you manage to determine the value of the content, there is no guarantee that this is the value you see in the UI.
You can try this:
public class ContentControlEx : ContentControl
{
protected override void OnContentChanged(object oldContent, object newContent)
{
// Do stuff...
base.OnContentChanged(oldContent, newContent);
}
}
Then use ContentControlEx in place of the regular one.

WPF DataGrid Need Vertical Scrollbar in Grid Row

My customer developing a property editor of sorts that contains multiple property sections that are presented as WPF Expanders with Datagrids contained within. We have everything laid out in a WPF Grid, currently with auto sized rows.
The problem we're trying to solve is proportional sizing of each property section when the screen height changes. When expanders are opened they should take up a proportional amount of space with vertical scrollbars kicking automatically when needed. When they are closed they should collapse and give the remaining space to the remaining sections.
In real life we have 4 expanders with content. The first one is a fixed height, the remaining three are Lists and DataGrids that need to size proportionally with the last one getting the largest portion of the remaining space. Keep in mind, users might resize the screen, or users might be running in different screen resolutions, so we need it to react accordingly.
We've tried messing with the DataGrid RowDefinition Height (fixed, proportional and auto) and MaxHeight, and also the MaxHeight of each datagrid, but I can't seem to find a combination that causes the whole area to be consumed when needed. We're researching a code based solution, but we're curious what others may suggest.
Here's a simple code sample that will give you the layout we're after. We just need it to work as described above.
Here's The Code (this would be a view model in real world)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
datagrid1.DataContext = GetCustomerVM();
datagrid2.DataContext = GetCustomerVM();
datagrid3.DataContext = GetCustomerVM();
}
private List<Customer> GetCustomerVM()
{
var CustomerList = new List<Customer>();
CustomerList.Add(new Customer() { FirstName = "A" });
CustomerList.Add(new Customer() { FirstName = "A" });
CustomerList.Add(new Customer() { FirstName = "A" });
CustomerList.Add(new Customer() { FirstName = "A" });
CustomerList.Add(new Customer() { FirstName = "A" });
CustomerList.Add(new Customer() { FirstName = "A" });
CustomerList.Add(new Customer() { FirstName = "A" });
CustomerList.Add(new Customer() { FirstName = "A" });
return CustomerList;
}
}
public class Customer
{
public string FirstName { get; set; }
}
The XAML
<Window x:Class="StackOverflow_SidePanelGridScrolling.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:StackOverflow_SidePanelGridScrolling"
mc:Ignorable="d"
Loaded="Window_Loaded"
Title="MainWindow" Height="500" Width="300">
<Window.Resources>
<DataTemplate x:Key="ExpanderHeaderTemplate">
<DockPanel Height="30">
<TextBlock Margin="4,0,0,0"
VerticalAlignment="Center"
DockPanel.Dock="Left"
FontSize="16"
Text="{Binding}" />
</DockPanel>
</DataTemplate>
<Style TargetType="Expander">
<Setter Property="HeaderTemplate"
Value="{StaticResource ExpanderHeaderTemplate}" />
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Expander x:Name="expander1" Grid.Row="0" Header="Area 1">
<DataGrid Name="datagrid1"
ItemsSource="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name"
Binding="{Binding FirstName}"
Width="100*" />
</DataGrid.Columns>
</DataGrid>
</Expander>
<Expander x:Name="expander2" Grid.Row="1" Header="Area 2">
<DataGrid Name="datagrid2"
ItemsSource="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name"
Binding="{Binding FirstName}"
Width="100*" />
</DataGrid.Columns>
</DataGrid>
</Expander>
<Expander x:Name="expander3" Grid.Row="3" Header="Area 3">
<DataGrid Name="datagrid2"
ItemsSource="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name"
Binding="{Binding FirstName}"
Width="100*" />
</DataGrid.Columns>
</DataGrid>
</Expander>
</Grid>
</Window>
As far as I'm able to understand your requirements, I think this does about what you want:
Every expander, when not expanded, takes up whatever minimal space it requires.
The top row, when expanded, is sized to its content.
Rows two, three, and four divide up the remainder of the space. When expanded, they stretch. Two gets one share of the available space, Three gets two shares, Four gets three shares. If Two and Three are open, Two gets one of four shares and Three gets the other three.
Those shares are handed out by the * Height values in the setters in the triggers. Here's the trigger for Row Four:
<DataTrigger
Binding="{Binding Children[3].IsExpanded, RelativeSource={RelativeSource AncestorType=Grid}}"
Value="True"
>
<Setter Property="Height" Value="3*" />
</DataTrigger>
The nice thing about doing this in XAML is that it will never spring any ugly surprises on you.
So here's the XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition>
<RowDefinition.Style>
<Style TargetType="RowDefinition">
<Setter Property="Height" Value="Auto" />
<Style.Triggers>
<DataTrigger
Binding="{Binding Children[1].IsExpanded, RelativeSource={RelativeSource AncestorType=Grid}}"
Value="True"
>
<Setter Property="Height" Value="1*" />
</DataTrigger>
</Style.Triggers>
</Style>
</RowDefinition.Style>
</RowDefinition>
<RowDefinition>
<RowDefinition.Style>
<Style TargetType="RowDefinition">
<Setter Property="Height" Value="Auto" />
<Style.Triggers>
<DataTrigger
Binding="{Binding Children[2].IsExpanded, RelativeSource={RelativeSource AncestorType=Grid}}"
Value="True"
>
<Setter Property="Height" Value="2*" />
</DataTrigger>
</Style.Triggers>
</Style>
</RowDefinition.Style>
</RowDefinition>
<RowDefinition>
<RowDefinition.Style>
<Style TargetType="RowDefinition">
<Setter Property="Height" Value="Auto" />
<Style.Triggers>
<DataTrigger
Binding="{Binding Children[3].IsExpanded, RelativeSource={RelativeSource AncestorType=Grid}}"
Value="True"
>
<Setter Property="Height" Value="3*" />
</DataTrigger>
</Style.Triggers>
</Style>
</RowDefinition.Style>
</RowDefinition>
</Grid.RowDefinitions>
<Expander
Grid.Row="0"
VerticalAlignment="Stretch"
Header="One" Background="LightGreen">
<StackPanel>
<Label>Content One</Label>
<Label>Content One</Label>
<Label>Content One</Label>
</StackPanel>
</Expander>
<Expander
Grid.Row="1"
VerticalAlignment="Stretch"
Header="Two" Background="LightSkyBlue">
<StackPanel>
<Label>Content Two</Label>
<Label>Content Two</Label>
<Label>Content Two</Label>
</StackPanel>
</Expander>
<Expander
Grid.Row="2"
VerticalAlignment="Stretch"
Header="Three" Background="LightGoldenrodYellow">
<StackPanel>
<Label>Content Three</Label>
<Label>Content Three</Label>
<Label>Content Three</Label>
</StackPanel>
</Expander>
<Expander
Grid.Row="3"
VerticalAlignment="Stretch"
Header="Four" Background="Khaki">
<StackPanel>
<Label>Content Four</Label>
<Label>Content Four</Label>
<Label>Content Four</Label>
</StackPanel>
</Expander>
</Grid>
These don't show scrollbars because I didn't take the time to create content that will scroll.

Binding child DataContext from within DataTrigger

I have been trying to develop a bar of RadioButtons (styled as ToggleButtons) which select which content is displayed in a ContentControl. I have been able to use a DataTrigger to display the proper view in the ContentControl based on which RadioButton is checked, but I am also trying to bind a view model from the parent DataContext into the child's DataContext, without success. A minimum sample is as follows:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" >
<RadioButton Name="rbShowChild" Content="Show Child" Style="{StaticResource {x:Type ToggleButton}}" />
</StackPanel>
<ContentControl Grid.Row="1">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=rbShowChild}" Value="True">
<Setter Property="Content">
<Setter.Value>
<local:ChildView DataContext="{Binding ChildViewModel, PresentationTraceSources.TraceLevel=High}"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</Window>
The Binding to the ChildViewModel seems to be the part that is not working as expected. For completeness, here is the related ChildView.
<UserControl x:Class="WpfApplication1.ChildView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel>
<TextBlock Text="Child" />
<TextBlock Text="{Binding Text}" />
</StackPanel>
</Grid>
</UserControl>
And the ViewModels:
namespace WpfApplication1
{
public class ChildViewModel
{
public string Text { get; set; }
public ChildViewModel()
{
Text = "It works!";
}
}
public class MainWindowViewModel
{
public ChildViewModel ChildViewModel { get; set; }
public MainWindowViewModel()
{
ChildViewModel = new ChildViewModel();
}
}
}
And the MainWindow DataContext is set as follows:
var window = new MainWindow()
{
DataContext = new MainWindowViewModel()
};
window.Show();
The Output window (with PresentationTraceSources.TraceLevel=High) shows this:
System.Windows.Data Error: 3 : Cannot find element that provides DataContext. BindingExpression:Path=ChildViewModel; DataItem=null; target element is 'ChildView' (Name=''); target property is 'DataContext' (type 'Object')
which leads me to think that somehow the MainWindow.DataContext is not the DataContext used to resolve the "{Binding ChildViewModel}" expression in the trigger, but I couldn't find any DataContext property on the trigger, nor have I been able to find anything in my searches to suggest an answer for this.
I would very much appreciate any suggestions to resolve this.
UPDATE: Looks like the issue is with how you are setting the data context.
Set the DataContext of the window in the XAML, then set the data context of the ContentControl to the ChildViewModel. Then set the actual displayed Content (AKA ChildView) Value. The ContentControl is the host of this view, so it should be the DataContext supplier. I myself am a little unsure the exact mechanics going on, but I know for fact the below code works.
NOTE Your XML namespaces may vary from the code below. Adjust accordingly.
<Window.DataContext>
<!-- Data Context added here OR in code-behind initialize-->
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" >
<RadioButton Name="rbShowChild" Content="Show Child" Style="{StaticResource {x:Type ToggleButton}}" />
</StackPanel>
<!-- Bind the ContentControls Data context-->
<ContentControl DataContext="{Binding ChildViewModel}" Grid.Row="1">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=rbShowChild}" Value="True">
<Setter Property="Content">
<Setter.Value>
<!-- Just simply set the view content here, no binding.-->
<local:ChildView />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
Doing this, I have run your application just fine in a project.

Make DatePicker easier to read when disabled

The text is grayed out when the DatePicker is disabled and I want the content to be easier to read.
What I did on some TextBoxes was:
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="Black" />
</Trigger>
</Style.Triggers>
</Style>
It did make the text easier to read.
I do manage to change the Foreground colour on the DataPicker but it does not do the trick. The text was still grayed out.
Seems like there is another property I need to set to make the content of the disabled DatePicker easier to read.
So, how do I make the content of my disabled DatePicker easier to read?
Can you extend DatePicker by adding bool DependencyProperty called Editable.
I found a working example at the following link, note that I run this code in .NET 4.
Here is the DatePicker Control:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
namespace DatePickerStyle
{
public class ExtendedDatePicker : DatePicker
{
public static readonly DependencyProperty EditableProperty = DependencyProperty.Register("Editable", typeof(bool),
typeof(ExtendedDatePicker), new PropertyMetadata(true));
public bool Editable
{
get { return (bool)GetValue(EditableProperty); }
set { SetValue(EditableProperty, value); }
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var textBox = GetTemplateChild("PART_TextBox") as DatePickerTextBox;
var binding = new Binding { Source = this, Path = new PropertyPath(ExtendedDatePicker.EditableProperty) };
textBox.SetBinding(UIElement.FocusableProperty, binding);
}
}
}
Here is the XAML:
<Window x:Class="DatePickerStyle.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:DatePickerStyle="clr-namespace:DatePickerStyle"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DatePicker IsEnabled="True" Grid.Row="0" SelectedDate="2002/12/31"/>
<DatePicker IsEnabled="False" Grid.Row="1" SelectedDate="2002/12/31"/>
<DatePickerStyle:ExtendedDatePicker Editable="True" Grid.Row="2" SelectedDate="2002/12/31"/>
<DatePickerStyle:ExtendedDatePicker Editable="False" Grid.Row="3" SelectedDate="2002/12/31"/>
</Grid>
</Window>
My I suggest this simpler, universal approach?
<ControlTemplate x:Key="MyDisabledDatePicker">
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock
Text="{Binding Path=SelectedDate, StringFormat={}{0:d}, RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center" HorizontalAlignment="Left" Padding="10,0,0,0"/>
</Border>
</ControlTemplate>
<Style TargetType="{x:Type DatePicker}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsEnabled, RelativeSource={RelativeSource Self}}" Value="false">
<Setter Property="Template" Value="{StaticResource MyDisabledDatePicker}" />
</DataTrigger>
</Style.Triggers>
</Style>
Presto!
the above code w ExtendedDatePicker works, first I thought it didn't but that was because the dropdown could still change the text and the Editable="False" doesn't work on the dropdown
so don't forget to add the following to the ExtendedDatePicker
Editable="False" AllowDrop="False" IsDropDownOpen="False" IsHitTestVisible="False" IsManipulationEnabled="False"

Resources