DataGrid RowDetails Width problem - wpf

Suppose I have a DataGrid that is defined like this
<DataGrid AreRowDetailsFrozen="True"
ItemsSource="{Binding MyCollection}"
AutoGenerateColumns="False">
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Border CornerRadius="5" BorderBrush="Red"
BorderThickness="2" Background="Black">
<TextBlock Foreground="White" Text="{Binding RowDetails}"
TextWrapping="Wrap"/>
</Border>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
<DataGrid.Columns>
<DataGridTextColumn Header="0" Binding="{Binding Value1}"/>
<DataGridTextColumn Header="1" Binding="{Binding Value2}"/>
<DataGridTextColumn Header="2" Binding="{Binding Value3}"/>
<DataGridTextColumn Header="3" Binding="{Binding Value4}"/>
</DataGrid.Columns>
</DataGrid>
And looks like this with and without RowDetails
In the picture to the right I get a very long DataGridRow that never wraps.
Is it possible to get the RowDetails to use the same width as the DataGrid and not effect the Width itself?
Things I have tried that achieves wrapping but not in a satisfying way
Set Width or MaxWidth on the Border or the TextBlock. Not very dynamic.
Set ScrollViewer.HorizontalScrollBarVisibility="Disabled" on the DataGrid. Not very good when the columns doesn't fit.

The answers here felt like a workaround so I did some research and did find the solution on the Telerik forums, since we use their RadGridView. Turned out the solution worked for DataGrid as well.
The key is to set the ScrollViewer.HorizontalScrollBarVisibility property to Disabled, see example below.
<DataGrid ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Border>
<TextBlock Foreground="White" Text="{Binding RowDetails}"
TextWrapping="Wrap"/>
</Border>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
Edit:
A side effect is that if the columns needs more space horizontally than there are room for they will be clipped. So if this is a problem then this solution isn't optimal.

This is what I ended up doing. I'd rather use a Property on the DataGrid for this but since no such Property exist I needed a workaround.
First I just used ActualWidth from the parent DataGrid and removed a constant of 9. This worked at first but failed when the vertical scrollbar became visible so I had to use a MultiBinding.
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Border HorizontalAlignment="Left" CornerRadius="5"
BorderBrush="Red" BorderThickness="2" Background="Black">
<Border.Width>
<MultiBinding Converter="{StaticResource RowDetailsWidthMultiConverter}"
ConverterParameter="9">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type DataGrid}}"
Path="ActualWidth"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ScrollViewer}}"
Path="ComputedVerticalScrollBarVisibility"/>
</MultiBinding>
</Border.Width>
<TextBlock Foreground="White" Text="{Binding RowDetails}" TextWrapping="Wrap"/>
</Border>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
And in the converter I used another constant (16) to compensate for a visible vertical scrollbar (if it's visible).
public class RowDetailsWidthMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double originalWidth = (double)values[0];
Visibility verticalScrollbarVisibility = (Visibility)values[1];
double subtractWidth = System.Convert.ToDouble(parameter);
double returnWidth = originalWidth - subtractWidth;
if (verticalScrollbarVisibility == Visibility.Visible)
{
return returnWidth - 16;
}
return returnWidth;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
Update
I improved on the solution a bit, using ActualWidth for the ItemsPresenter rather then DataGrid (where ActualWidth didn't change depending on a visible ScrollBar), thus removing the need for a MultiConverter and two constants.
<DataGrid.Resources>
<local:SubtractConstantConverter x:Key="SubtractConstantConverter"/>
</DataGrid.Resources>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Border HorizontalAlignment="Left" CornerRadius="5"
BorderBrush="Red" BorderThickness="2" Background="Black"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsPresenter}},
Path=ActualWidth,
Converter={StaticResource SubtractConstantConverter},
ConverterParameter=6}">
<TextBlock Foreground="White" Text="{Binding RowDetails}" TextWrapping="Wrap"/>
</Border>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
SubtractConstantConverter
public class SubtractConstantConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double originalValue = (double)value;
double subtractValue = System.Convert.ToDouble(parameter);
return originalValue - subtractValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}

This is what I ended up doing: binding of the row details width to the actual width of its presenter and then added a border with a varied thickness to compensate for the presence/absence of a vertical scroll-bar in the presenter. This approach worked perfectly for me. Sample xaml:
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Border BorderThickness="2,2,8,2"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsPresenter}}, Path=ActualWidth}"
HorizontalAlignment="Left" >
<!-- add the row details view contents here -->
</Border>
</DataTemplate>
</DataGrid.RowDetailsTemplate>

You might be able to bind the MaxWidth to ElementName=PART_ColumnHeadersPresenter, Path=ActualWidth or perhaps RenderSize.Width. I believe that is the part of the DataGrid Template that displays the columns so in theory it should work

Thanks Meleak, your solution worked well for me. One small addition for us WPF newbies. Be sure to declare your Converter class as a resource so it can be referenced in the Binding expression.
I put mine in App.Xaml like this:
<Application x:Class="ISCBilling.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="clr-namespace:my.application.namespace"
StartupUri="IscBillingWindow.xaml">
<Application.Resources>
<conv:RowDetailsWidthMultiConverter x:Key="RowDetailsWidthMultiConverter" />
</Application.Resources>
</Application>

To save other folks some head scratching and trial-and-error time:
After fussing with Fredrik Hedblad's most recent (1/1/11) solution for some time I figured out that the ConverterParameter value should be 6 + [left margin} + [right margin] (i.e. margins of the outermost container in the template.) After examining a blowup of a screen shot, I expect the 6 is the width of the vertical bar at the left of each row.

Related

how to set heights of 3 DataGrids inside Extenders in one StackPanel in MVVM dynamically?

The heights of each DataGrid should depend on:
Are other Extenders extended
How many rows the DataGrid has
If other Extenders are extended, how many rows other DataGrids have.
Height of the StackPanel the Extenders are in
All Extenders should be always visible.
If e.g. opening only upmost Extender and its DataGrid has 100 rows the Height of the DataGrid should be almost the height of StackPanel but in the bottom the two other Extenders should be visible and upmost DataGrid scrollable.
When opening 2 or 3 Extenders the StackPanel should be "full" if there is enough data in DataGrids.
I tried to bind the height properties (for undermost expander Objec information)
<Expander Header="Object information" IsExpanded="{Binding ObjectInformationExpanded}">
<DataGrid Name="ObjectInformationGrid" CanUserAddRows="False" CanUserResizeColumns="True" CanUserSortColumns="True" IsReadOnly="True"
ItemsSource="{Binding SelectedObjectAttributes}" AutoGenerateColumns="False"
Height="{Binding ObjectInformationHeight, Mode=TwoWay}"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Visible">
<DataGrid.Columns>
<DataGridTextColumn Header="Field" Binding="{Binding Item1}" />
<DataGridTextColumn Header="Value" Binding="{Binding Item2}" />
</DataGrid.Columns>
</DataGrid>
</Expander>
But now my ViewModel has lots of code that is related to presentation (View) and still didn't take the StackPanel's height into the calculation. Instead I set the hardcoded double values which can't be good solution.
Here is bit of simple code that updates the bound height Properties (here for upmost DataGrid only) when extenders are opened/closed and DataGrid bound collections are changed:
private void UpdateHeights()
{
if (SelectedObjectsExpanded == true)
{
if (_selectedObjects.Count == 0)
{
SelectedObjectsHeight = double.NaN;
}
else if (_selectedObjects.Count < 8)
{
SelectedObjectsHeight = 100;
}
else
{
if (ObjectInformationExpanded && SelectedSwitchesExpanded)
SelectedObjectsHeight = 100;
else
SelectedObjectsHeight = 200;
}
}
//...
Thanks!
Would something like this work?
The basic idea is put all 3 Expanders in a Grid containing 3 rows, and bind the RowDefinition.Height of each row to the Expander.IsExpanded. A Converter is used to set the value to Auto if the expander is collapsed (only take up the space needed), or * if it's expanded (take up all remaining space. If multiple rows are set to * height, share space evenly between them).
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding IsExpanded, ElementName=Expander1, Converter={x:Static MyBoolToGridSizeConverter}}" />
<RowDefinition Height="{Binding IsExpanded, ElementName=Expander2, Converter={x:Static MyBoolToGridSizeConverter}}" />
<RowDefinition Height="{Binding IsExpanded, ElementName=Expander3, Converter={x:Static MyBoolToGridSizeConverter}}" />
</Grid.RowDefinitions>
<Expander Grid.Row="0" x:Name="Expander1">
<DataGrid />
</Expander>
<Expander Grid.Row="1" x:Name="Expander2">
<DataGrid />
</Expander>
<Expander Grid.Row="2" x:Name="Expander3">
<DataGrid />
</Expander>
</Grid>
public class BoolToGridSizeConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value is bool && (bool)value)
return new GridLength(1, GridUnitType.Star);
return GridLength.Auto;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
// this is not needed
}
}

Prevent empty tooltips at a wpf datagrid

I am working on a calendar program, which consists mainly of a WPF DataGrid. As there is not always enough space to display all the entries of a day (which is a DataGridCell), a tooltip with all the entries of the day shell appear at mouse over. This works so far with the code snippet shown below. And now the (little) problem: If there are no entries for a day, no tooltip shell pop up. With the code below an empty tooltip pops up.
<DataGridTemplateColumn x:Name="Entry"
IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding EntryText}"
Foreground="{Binding EntryForeground}"
FontWeight="{Binding EntryFontWeight}">
</TextBlock>
<TextBlock Text="{Binding RightAlignedText}"
Foreground="Gray"
Background="Transparent">
<TextBlock.ToolTip>
<TextBlock Text="{Binding AllEntriesText}"/>
</TextBlock.ToolTip>
</TextBlock>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The Databinding is made via
myCalDataGrid.Itemssource = _listOfDays;
in code behind, where a 'Day' is the view model for a single calendar row.
As H.B. suggested bind directly to the ToolTip property instead of using TextBlock and in case AllEntriesText is empty string you can apply a trigger on your TextBlock to disable your tooltip by setting the property ToolTipService.IsEnabled like this -
<TextBlock Text="{Binding RightAlignedText}"
Foreground="Gray"
Background="Transparent"
ToolTip="{Binding AllEntriesText}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="ToolTip"
Value="{x:Static system:String.Empty}">
<Setter Property="ToolTipService.IsEnabled" Value="False" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Make sure to add namespace system in your xaml -
xmlns:system="clr-namespace:System;assembly=mscorlib"
Bind directly to the ToolTip property (do not create a TextBlock for it) and set AllEntriesText to null if there are no entries, then the ToolTip itself is also null and should not show.
Thanks for the solutions, they may work, no question. But I need a TextBlock for the tooltip to format and align the text. So I found this solution:
<TextBlock Text="{Binding RightAlignedText}"
HorizontalAlignment="Stretch"
TextAlignment="Right" Padding="2,0"
Foreground="Gray"
Background="Transparent"
ToolTipService.ShowDuration="60000"
ToolTipService.BetweenShowDelay="0"
ToolTipService.InitialShowDelay="0"
>
<TextBlock.ToolTip>
<ToolTip Visibility="{Binding EntryToolTipVisibility}">
<TextBlock Text="{Binding ToolTipText}"
TextAlignment="Left"
FontFamily="Courier New"/>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
I bound the propertie "Visibility" of tooltip to a propertie "EntryToolTipVisibility" (of type Visibility) in the view model. See code snippet below.
public Visibility EntryToolTipVisibility
{
get
{
return _entries.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
}
}
Another option is to use an own Converter.
I prefer this way for example for a TextBlock tooltip that displays TextBlock's text, but for the case there is no text, the empty tooltip is not desired.
XAML code:
//step #1
xmlns:local="clr-namespace:MyNamespace"
//step #2 - into Window.Resources or other
<local:StringToVisibleTooltip x:Key="StringToVis" />
//step #3 - example of use
<TextBlock ...other attributes... TextTrimming="CharacterEllipsis">
<TextBlock.ToolTip>
<ToolTip Visibility="{Binding Path=Text, Converter={StaticResource StringToVis}}">
<TextBlock Text="{Binding Text}"/>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
And code behind
namespace MyNamespace
{
public class StringToVisibleTooltip : IValueConverter
{
public StringToVisibleTooltip() { }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && ((string)value).Length > 0) //empty string does not need tooltip
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
}

Use different template for last item in a WPF itemscontrol

I'm using a custom template in my itemscontrol to display the following result:
item 1, item 2, item3,
I want to change the template of the last item so the result becomes:
item 1, item2, item3
The ItemsControl:
<ItemsControl ItemsSource="{Binding Path=MyCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text=", "/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Is there anyone who can give a solution for my problem? Thank you!
I've found the solution for my problem using only XAML. If there is anybody who needs to do the same, use this:
<ItemsControl ItemsSource="{Binding Path=MyCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="comma" Text=", "/>
<TextBlock Text="{Binding}"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData}}" Value="{x:Null}">
<Setter TargetName="comma" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You can use DataTemplateSelector, in SelectTemplate() method you can check whether item is the last and then return an other template.
In XAML:
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentPresenter
ContentTemplateSelector = "{StaticResource MyTemplateSelector}">
In Code behind:
private sealed class MyTemplateSelector: DataTemplateSelector
{
public override DataTemplate SelectTemplate(
object item,
DependencyObject container)
{
// ...
}
}
This solution affects the last row and updates with changes to the underlying collection:
CodeBehind
The converter requires 3 parameters to function properly - the current item, the itemscontrol, the itemscount, and returns true if current item is also last item:
class LastItemConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
int count = (int)values[2];
if (values != null && values.Length == 3 && count>0)
{
System.Windows.Controls.ItemsControl itemsControl = values[0] as System.Windows.Controls.ItemsControl;
var itemContext = (values[1] as System.Windows.Controls.ContentPresenter).DataContext;
var lastItem = itemsControl.Items[count-1];
return Equals(lastItem, itemContext);
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML
The Data-Trigger for a DataTemplate, that includes a textbox named 'PART_TextBox':
<DataTemplate.Triggers>
<DataTrigger Value="True" >
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource LastItemConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}" />
<Binding RelativeSource="{RelativeSource Self}"/>
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}" Path="Items.Count"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Foreground" TargetName="PART_TextBox" Value="Red" />
</DataTrigger>
</DataTemplate.Triggers>
The converter as a static resource in the Xaml
<Window.Resources>
<local:LastItemConverter x:Key="LastItemConverter" />
</Window.Resources>
SnapShot
And a snapshot of it in action
The code has been added to the itemscontrol from this 'codeproject'
https://www.codeproject.com/Articles/242628/A-Simple-Cross-Button-for-WPF
Note the last item's text in red
One question... I see you're using an ItemsControl as opposed to say a ListBox and that it appears to be bound to a collection of strings, and that you're only trying to display the resulting text without formatting the individual parts, which makes me wonder if your desired output is actually the string itself as mentioned in the question, and not an actual ItemsControl per se.
If I'm correct about that, have you considered just using a simple TextBlock bound to the items collection, but fed through a converter? Then Inside the converter, you would cast value to an array of strings, then in the Convert method, simply Join them using a comma as the separator which will automatically, only add them between elements, like so...
var strings = (IEnumerable<String>)value;
return String.Join(", ", strings);

Silverlight: How can I update an IValueConverter bound Textbox upon changing values in an Entity?

So, I have a DataGrid with a TextBlock that displays the aggregrate value of two textboxes in the grid. I do this by binding using a value converter. This works on the load but I need it to update upon changing the values of the other entities which its aggregating over. Here is some of my code:
Here is the my PagedCollectionView in my ViewModel which is bound to the View.
private PagedCollectionView _grievances;
public PagedCollectionView Grievances
{
get
{
if (_grievances == null)
{
_grievances = new PagedCollectionView(Context.lict_grievances);
_grievances.SortDescriptions.Add(new SortDescription("grievance_type_id", ListSortDirection.Ascending));
}
return _grievances;
}
}
Here is my the DataGrid in my View:
<sdk:DataGrid x:Name="grdGrievances" AutoGenerateColumns="False" ItemsSource="{Binding Path=Grievances}" HorizontalContentAlignment="Center">
<sdk:DataGrid.Columns>
<sdk:DataGridTemplateColumn Header="Total # of Outcomes">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource GrievanceOutcomeSum}}" Margin="15,0,0,0"></TextBlock>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn Header="Resolved">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=outcome_resolved, Mode=TwoWay}"
TextChanged="ResolvedTextBox_TextChanged" HorizontalAlignment="Center"></TextBox>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn Header="Pending">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=outcome_pending, Mode=TwoWay}"></TextBox>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
Here is my Value Converter for the aggregated textblock:
public class GrievancesAggregateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
lict_grievance entity = (lict_grievance)value;
short i = 0;
if (entity != null)
{
if (entity.outcome_resolved != null)
i += (short)entity.outcome_resolved;
if (entity.outcome_pending != null)
i += (short)entity.outcome_pending;
}
return i;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
So, upon changing the values in the other 2 textboxes I need it to refresh the aggregrated value in the textblock. How can I accomplish this? Im at a loss right now and browsing the web I couldnt find any solutions.
Thanks a bunch,
Evan
Evan, Colin Eberhardt has an interesting so called "MultiBinding" solution on his blog and is doing something very similar.
"MultiBinding is a WPF feature that
allows you to bind a single property
to a number of sources, with the
source values being combined by a
value converter. This is a feature
that is missing from Silverlight"
(Quote)
This should solve your problem. Best regards!
Why don't you bind the TextBlock (Total # of Outcomes) to a property that will fire PropertyChanged. Then set that value from the ResolvedTextBox_TextChanged event.
Try something like this:
<StackPanel Orientation="Vertical">
<i:Interaction.Triggers>
<ei:PropertyChangedTrigger Binding="{Binding FirstTextBox}">
<ei:ChangePropertyAction TargetObject="{Binding ElementName=textBlock}"
PropertyName="Text"
Value="{Binding Converter={StaticResource TestConverter}}" />
</ei:PropertyChangedTrigger>
<ei:PropertyChangedTrigger Binding="{Binding SecondTextBox}">
<ei:ChangePropertyAction TargetObject="{Binding ElementName=textBlock}"
PropertyName="Text"
Value="{Binding Converter={StaticResource TestConverter}}" />
</ei:PropertyChangedTrigger>
</i:Interaction.Triggers>
<TextBlock x:Name="textBlock" />
<TextBox x:Name="firstTextBox"
Text="{Binding FirstTextBox, Mode=TwoWay}" />
<TextBox x:Name="secondTextBox"
Text="{Binding SecondTextBox, Mode=TwoWay}" />
</StackPanel>
try calling OnApplyTemplate
eg. grid.OnApplyTemplate()

Expand/Collapse all expanders

I have a list of expanders which I want to control its expanded state(IsExpanded) with global toggle button which should toggle between expanded/collapsed state.
The solution which I have got so far does that by binding the expander's IsExpanded state to the togglebutton's IsChecked state. This works as long as I dont manually dont toggle the expanders. Once I do that those particular expanders dont respect the binding (toggle button's IsChecked state).
Any idea why? and is there a clean solution in XAML for this?
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<StackPanel>
<ToggleButton Name="ExpandAll">Toggle</ToggleButton>
<Expander IsExpanded="{Binding ElementName=ExpandAll,Path=IsChecked, Mode=OneWay}">
Hai
</Expander>
<Expander IsExpanded="{Binding ElementName=ExpandAll,Path=IsChecked, Mode=OneWay}">
Hello
</Expander>
<Expander IsExpanded="{Binding ElementName=ExpandAll,Path=IsChecked, Mode=OneWay}">
Weird
</Expander>
</StackPanel>
</Grid>
</Page>
I know that this post is very old. Just posting this for anyone else who comes across. The below code worked for me.
<Expander IsExpanded="{Binding ElementName=ExpandAll, Path=IsChecked, UpdateSourceTrigger=Explicit}">
</Expander>
This works when the expanders are generated dynamically, for example inside a DataGrid.RowDetailsTemplate.
I don't think you can achieve this entirely in XAML, but the following allows you to do it with an IValueConverter:
<StackPanel>
<StackPanel.Resources>
<local:Converter x:Key="Converter" />
</StackPanel.Resources>
<ToggleButton Name="ExpandAll">
<ToggleButton.IsChecked>
<MultiBinding Mode="OneWayToSource" Converter="{StaticResource Converter}">
<Binding ElementName="Expander1" Path="IsExpanded" />
<Binding ElementName="Expander2" Path="IsExpanded" />
<Binding ElementName="Expander3" Path="IsExpanded" />
</MultiBinding>
</ToggleButton.IsChecked>
Toggle</ToggleButton>
<Expander Name="Expander1">
Hai
</Expander>
<Expander Name="Expander2">
Hello
</Expander>
<Expander Name="Expander3">
Weird
</Expander>
</StackPanel>
And your Converter is as below:
public class Converter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//we're using OneWayToSource, so this will never be used.
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
// we want to convert the single 'IsChecked' value from the ToggleButton into
// 3 'IsExpanded' values
var allValues = new object[targetTypes.Length];
for (int i = 0; i < allValues.Length; i++)
{
allValues[i] = value;
}
return allValues;
}
}
This works by setting up a OneWayToSource binding between the IsChecked property of the ToggleButton (i.e. the binding will be set against the source when the target value changes), and uses an IMultiValueConverter to translate the single value into one for each of the Expanders.

Resources