How to flash error text using WPF animation - wpf

I have textbox displayed using the DataTemplate. In the below code TextBoxViewModel is derived from IDataErrorInfo.
protected String m_ValidationErrorMessage;
protected String ValidationErrorMessage
{
get { return m_ValidationErrorMessage; }
}
private bool m_ShowErrorMessage = false;
public bool ShowErrorMessage
{
get { return m_ShowErrorMessage; }
set { this.m_ShowErrorMessage = value; }
}
private String m_DisplayValue;
public String DisplayValue
{
get { return this.m_DisplayValue; }
set
{
if (m_DisplayValue != value)
{
if (IsTextValid(value, out m_ValidationErrorMessage))
{
// Set data to model
this.SendPropertyChangedEvent(nameof(this.DisplayValue));
}
else
{
ShowErrorMessage = true;
}
}
}
}
public string this[string columnName]
{
get
{
if (columnName == nameof(this.DisplayValue))
{
if (ShowErrorMessage)
{
return ValidationErrorMessage;
}
}
return null;
}
}
xaml for TextBoxViewModel
<DataTemplate DataType="{x:Type vms:TextBoxViewModel}">
<DockPanel>
<Label Content="{Binding Path=DisplayName}" MinWidth="120" MaxWidth="150"/>
<TextBox DockPanel.Dock="Left" Text="{Binding Path=DisplayValue,Mode=TwoWay, UpdateSourceTrigger=Default,ValidatesOnDataErrors=True}" Validation.ErrorTemplate="{StaticResource ErrorTemplate}"/>
</DockPanel>
</DataTemplate
<ControlTemplate x:Key="ErrorTemplate">
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder x:Name="textBox"/>
<ItemsControl ItemsSource="{Binding}" VerticalAlignment="Center" HorizontalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}" Foreground="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
I am trying to flash the error text for few seconds and hiding it after that. Is there any way to achieve this using the XAML (WPF animation)?

You could for example animate the Opacity property of the TextBlock using a DoubleAnimation. Something like this:
<ControlTemplate x:Key="ErrorTemplate">
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder x:Name="textBox"/>
<ItemsControl ItemsSource="{Binding}" VerticalAlignment="Center" HorizontalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}" Foreground="Red">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="0" To="1"
AutoReverse="False"
Duration="0:0:0.5"
RepeatBehavior="3x" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>

Related

How to properly use radiobutton checked event in mvvm pattern?

When a particular radiobutton is checked/clicked on I want to write some specific text for each of those radiobuttons in a particular textblock. How do I do that following the MVVM pattern ?
In my main viewmodel I have the following code...
public ICommand MyCommand { get; set; }
private string _txt;
public string Txt
{
get
{
return _txt;
}
set
{
_txt = value;
OnPropertyChanged("Txt");
}
}
private bool canexecutemethod(object parameter)
{
if (parameter != null)
{
return true;
}
else
{
return false;
}
}
private void executemethod(object parameter)
{
//what to do here;
}
public MainViewModel()
{
MyCommand = new RelayCommand(executemethod);
}
Also in the xaml:
<RadioButton VerticalAlignment="Center" Margin="15" Content="Name" Command="{Binding MyCommand}"/>
<RadioButton VerticalAlignment="Center" Margin="15" Content="Age" Command="{Binding MyCommand}"/>
<RadioButton VerticalAlignment="Center" Margin="15" Content="DOB" Command="{Binding MyCommand}"/>
<TextBlock Margin="5" Height="30" Width="150" Foreground="Red" Text="{Binding Txt, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
Say when the first radiobutton is checked the textblock text should show some text like
Please enter a name
, when the second radiobutton is checked the textblock text should show some text like
Please enter a valid age/integer value
and so on...
it can be done without view model participation:
<RadioButton VerticalAlignment="Center" Margin="15" Content="Name" Name="chkName"/>
<RadioButton VerticalAlignment="Center" Margin="15" Content="Age" Name="chkAge"/>
<RadioButton VerticalAlignment="Center" Margin="15" Content="DOB" Name="chkDob"/>
<TextBlock Margin="5" Height="30" Width="150" Foreground="Red">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=chkName,Path=IsChecked}" Value="True">
<Setter Property="Text" Value="Please enter a name"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=chkAge,Path=IsChecked}" Value="True">
<Setter Property="Text" Value="Please enter a valid age/integer value"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>

<EventTrigger RoutedEvent="Binding.TargetUpdated"> not firing with ContentTemplateSelector

I'm trying to animate the Background Color of a Border in a DataTemplate for a DataObject when a Child Property of the DataObject changes.
The DataObject is a Class called Test with two Properties, Number and Text.
I have an ObservableCollection of DataObjects called Numbers.
In a Task I update the Number Property at a regular Interval.
<Window
x:Class="WpfAnimationTest.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:local="clr-namespace:WpfAnimationTest"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
DataContext="{Binding Main, Source={StaticResource Locator}}"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="NumberTemplate">
<TextBlock Text="{Binding NotifyOnTargetUpdated=True}" />
</DataTemplate>
<local:ValueTemplateSelector x:Key="TemplateSelector">
<local:ValueTemplateSelector.NumberTemplate>
<DataTemplate>
<ContentControl Content="{Binding NotifyOnTargetUpdated=True}" ContentTemplate="{StaticResource NumberTemplate}" />
</DataTemplate>
</local:ValueTemplateSelector.NumberTemplate>
</local:ValueTemplateSelector>
<DataTemplate DataType="{x:Type local:Test}">
<Border x:Name="UpdateBorder" Background="Aqua">
<StackPanel Orientation="Horizontal">
<TextBlock
Width="50"
Margin="10"
Text="{Binding Text}" />
<ContentControl
Width="50"
Margin="10"
Content="{Binding Number}"
ContentTemplateSelector="{StaticResource TemplateSelector}" />
<!--
ContentTemplate="{StaticResource NumberTemplate}"
-->
</StackPanel>
</Border>
<DataTemplate.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard AutoReverse="True">
<ColorAnimation
FillBehavior="Stop"
Storyboard.TargetName="UpdateBorder"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="#C5AFFFAA"
Duration="00:00:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Numbers}" />
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
namespace WpfAnimationTest
{
public class Locator
{
public Locator()
{
Main = new Main();
}
public Main Main { get; set; }
}
public class Main
{
public ObservableCollection<Test> Numbers { get; set; } = new ObservableCollection<Test>();
public Main()
{
var rnd = new Random(42);
for (int i = 0; i < 10; i++)
{
Numbers.Add(new Test(){Number = i, Text = $"#: {i}"});
}
Task.Run(() =>
{
while (true)
{
try
{
Application.Current?.Dispatcher.Invoke(() =>
{
Numbers[rnd.Next(9)] = new Test
{
Number = rnd.Next(30),
Text = $"# {rnd.Next(30) + 30}"
};
});
Thread.Sleep(1000);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
});
}
}
public class Test
{
public int Number { get; set; }
public string Text { get; set; }
}
public class ValueTemplateSelector : DataTemplateSelector
{
/// <summary>
///
/// </summary>
/// <param name="item"></param>
/// <param name="container"></param>
/// <returns></returns>
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return !(item is Test value)
? null
: NumberTemplate;
}
/// <summary>
///
/// </summary>
public DataTemplate NumberTemplate { get; set; }
/// <summary>
///
/// </summary>
public DataTemplate DefaultTemplate { get; set; }
}
}
When using a ContentTemplate for the Number Property in the ContentControl the animation is working.
But when I use a ContentTemplateSelector the Animation is not triggered anymore.
What am I missing here?
Your data bindings are wrong, resulting in unhandled or unexpected data types.
Currently, your ContentControl.Content property (inside the DataTemplate for the type Test) binds to the Test.Number property of type int. Therefore, the object that is passed to the DataTemplateSelector is of type int and not Test. The condition in the DataTemplateSelector.SelectTemplate method will return false (as int is not Test is true) and make the DataTemplateSelector.SelectTemplate return null - no template. The XAML engine will call ToString on the int instance and is therefore able to display the value correctly (despite the lack of a DataTemplate).
Solution
You must fix the binding on the ContentControl.Content property to bind to the Test instance instead of binding to the Test.Number property:
<DataTemplate DataType="{x:Type local:Test}">
<Border x:Name="UpdateBorder"
Background="Aqua">
<StackPanel Orientation="Horizontal">
<TextBlock Width="50"
Margin="10"
Text="{Binding Text}" />
<ContentControl Width="50"
Margin="10"
Content="{Binding}"
ContentTemplateSelector="{StaticResource TemplateSelector}" />
</StackPanel>
</Border>
...
</DataTemplate>
Now that that the DataTemplateSelector can work properly, you must also adjust the NumberTemplate, so that it can display the Test.Number property properly. Since we have modified the binding source for the ContentControl.Content property, the new data type is now Test (instead of int):
<DataTemplate x:Key="NumberTemplate"
DataType="{x:Type Test}">
<TextBlock Text="{Binding Number, NotifyOnTargetUpdated=True}" />
</DataTemplate>
Remarks
There is a lot of redundant code here. You will achieve the same results by dropping all the templates and the DataTemplateSelector by defining the ItemTemplate for the Test items properly.
The following drastically simplified version should also work:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:Test}">
<Border x:Name="UpdateBorder"
Background="Aqua">
<StackPanel Orientation="Horizontal">
<TextBlock Width="50"
Margin="10"
Text="{Binding Text}" />
<TextBlock Width="50"
Margin="10"
Text="{Binding Number, NotifyOnTargetUpdated=True}" />
</StackPanel>
</Border>
<DataTemplate.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard AutoReverse="True">
<ColorAnimation FillBehavior="Stop"
Storyboard.TargetName="UpdateBorder"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="#C5AFFFAA"
Duration="00:00:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Numbers}" />
</Grid>

Images aren't displayed in WPF ListBox

I'm trying to display images in a ListView. The images are being loaded through Entity Framework from a SQL Server database. Somehow the images don't get displayed in the ListView and I can't figure out why.
I've created a test Visual Studio project.
The images are stored in an ObservableCollection<ImageViewModel>:
private ObservableCollection<ImageViewModel> images;
public ObservableCollection<ImageViewModel> Images
{
get { return images; }
set { images = value; }
}
Please let me know if you need any further details. I'm grateful for any hints that I could try out.
I'll add a screenshot as soon as I have 10 reputation.
The ImageViewModel class looks like this:
using Common;
using TestImage.Model;
namespace TestImage.ViewModel
{
class ImageViewModel : ObservableObject
{
public ImageViewModel()
{
image = new ImageTable() { Name = "unknown"};
}
#region Properties
private ImageTable image;
public ImageTable Image
{
get { return image; }
set { image = value; }
}
public string Name
{
get { return image.Name; }
set
{
if (image.Name != value)
{
image.Name = value;
RaisePropertyChanged("Name");
}
}
}
public byte[] ImageBinary
{
get { return image.ImageBinary; }
set
{
if (image.ImageBinary != value)
{
image.ImageBinary = value;
RaisePropertyChanged("ImageBinary");
}
}
}
#endregion
}
}
The MainWindow.xaml looks like this:
<Window x:Class="TestImage.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestImage.ViewModel"
Title="MainWindow" Height="375.6" Width="525">
<Window.DataContext>
<local:ImageLibrary/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="{x:Type ListBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="4" CornerRadius="5" Margin="6">
<Image Source="{Binding Images}" Stretch="Fill" Width="100" Height="120" />
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
</Style>
</Window.Resources>
<Grid Margin="0,0,0.4,1.4">
<ListBox Name="ListBoxImages" ItemsSource="{Binding Images}" Margin="413,40,20,31.6"/>
<Image Name="ImageSource" Source="{Binding SelectedImage}" HorizontalAlignment="Left" Height="148" Margin="14,90,0,0" VerticalAlignment="Top" Width="174"/>
<Button Name="ButtonPick" Content="Pick" HorizontalAlignment="Left" Margin="14,266,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.563,-0.731" Command="{Binding CommandPickImage}"/>
<Button Name="ButtonSave" Content="Save" HorizontalAlignment="Left" Margin="113,266,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.563,-0.731" Command="{Binding CommandSaveImage}" />
<Label Name="LabelFileName" Content="FileName" HorizontalAlignment="Left" Margin="14,40,0,0" VerticalAlignment="Top" Width="60"/>
<TextBox Name="TextBoxFileName" HorizontalAlignment="Left" Height="26" TextWrapping="Wrap" VerticalAlignment="Top" Width="314" Margin="79,40,0,0" Text="{Binding SelectedFileName}"/>
<Button Name="ButtonLoad" Content="Load" HorizontalAlignment="Left" Margin="113,293,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.563,-0.731" Command="{Binding CommandLoadImage}" />
<Image Source="{Binding SelectedImageDatabase}" HorizontalAlignment="Left" Height="148" Margin="193,90,0,0" VerticalAlignment="Top" Width="100"/>
</Grid>
</Window>
The binding in the ListBox ItemTemplate should be to the ImageViewModel's ImageBinary property, instead of Images:
<DataTemplate>
<Border ...>
<Image Source="{Binding ImageBinary}" .../>
</Border>
</DataTemplate>

WPF Sorting ItemsControl under DataTemplate

I am using ItemsControl under DataTemplate. I want to sort ItemsControl ic using id column.
<DataTemplate x:Key="With">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBlock Text="{Binding Path=fil}" Style="{StaticResource Fixed}" Margin="0,0,6,8" />
<mui:ModernButton IconData="{StaticResource PlayIconData}" Click="FullPlayback" Margin="0,0,6,8" ></mui:ModernButton>
</StackPanel>
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal">
<TextBlock Text="{Binding Path=e1}" Foreground="Red" Style="{StaticResource Fixed}" Margin="0,0,6,8" />
<TextBlock Text="{Binding Path=m1}" Foreground="LightSalmon" Style="{StaticResource Fixed}" Margin="0,0,6,8" />
<TextBlock Text="{Binding Path=n1}" Foreground="Orange" Style="{StaticResource Fixed}" Margin="0,0,6,8" />
<TextBlock Text="{Binding Path=m2}" Foreground="LightGreen" Style="{StaticResource Fixed}" Margin="0,0,6,8" />
<TextBlock Text="{Binding Path=m3}" Foreground="Green" Style="{StaticResource Fixed}" Margin="0,0,6,8" />
<TextBlock Text="{Binding ElementName=H1, Path=Items.Count,Mode=OneWay}" Style="{StaticResource Fixed}" Margin="0,0,6,8" />
</StackPanel>
<ItemsControl Name="ic" DockPanel.Dock="Bottom" ItemsSource="{Binding Path=seg}" ItemsPanel="{StaticResource HSPanel}">
<ControlTemplate TargetType="ItemsControl">
<Border>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsPresenter />
</ScrollViewer>
</Border>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
</DockPanel>
</DataTemplate>
i tried below options but sorting is not working.
1.Tried sorting in constructor of the user control like following (code behind)
ic.Items.SortDescriptions.Clear();
ic.Items.SortDescriptions.Add(new SortDescription("id", ListSortDirection.Ascending));
ic.Items.Refresh();
But i am unable to access ic in code behind. Errors says "ic does not exists in current context"
2.Tried CollectionViewSource under ItemsControl in xaml which is also not working.
<ItemsControl x:Name="ic" DockPanel.Dock="Bottom" ItemsSource="{Binding Path=segments}" ItemsPanel="{StaticResource HSPanel}">
<ItemsControl.Resources>
<CollectionViewSource x:Key="segments" Source="{Binding seg}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="id" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</ItemsControl.Resources>
<ItemsControl.Template>
3.Tried CollectionViewSource under ControlTemplate in xaml which is also not working.
<ControlTemplate TargetType="ItemsControl">
<ControlTemplate.Resources>
<CollectionViewSource x:Key="segments" Source="{Binding seg}" >
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="sortId" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</ControlTemplate.Resources>
But i initialised Loaded event of ic and tried to do sorting from there.In this case initially when the page loads, the items are not sorted. But when i move to another user control and come back to this current user control, the items looks sorted out perfectly.
private void ic_Loaded(object sender, RoutedEventArgs e)
{
ItemsControl ic = (ItemsControl)sender;
ic.Items.SortDescriptions.Clear();
ic.Items.SortDescriptions.Add(new SortDescription("id", ListSortDirection.Ascending));
ic.Items.Refresh();
}
You have two options :
1 - Sort your source collection (seg) in View Model.
2 - Use CollectionViewSource (http://msdn.microsoft.com/fr-fr/library/system.windows.data.collectionviewsource.aspx).
Here is a full working exemple:
I have added this code in to an empty WPF window:
public class SomeVM
{
public ObservableCollection<SomeItemVM> Items { get; set; }
public SomeVM()
{
Items = new ObservableCollection<SomeItemVM>();
}
}
public class SomeItemVM
{
public string id { get; set; }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//Create some VM
SomeVM data = new SomeVM();
data.Items.Add(new SomeItemVM() { id = "3" });
data.Items.Add(new SomeItemVM() { id = "4" });
data.Items.Add(new SomeItemVM() { id = "1" });
data.Items.Add(new SomeItemVM() { id = "2" });
this.DataContext = data;
}
}
Then in XAML I add a content control that will hold the VM and a DataTemplate that will describe the way the VM will be displayed:
<Window.Resources>
<DataTemplate x:Key="With">
<DockPanel>
<DockPanel.Resources>
<!-- CollectionViewSource should be declared as a resource of parent container of the ItemsControl.
Otherwise there will be an exception of StaticResourceHolder -->
<CollectionViewSource x:Key="segments" Source="{Binding Items}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="id" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</DockPanel.Resources>
<ItemsControl Name="ic" DockPanel.Dock="Bottom" ItemsSource="{Binding Source={StaticResource segments}}">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<Border>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsPresenter />
</ScrollViewer>
</Border>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding id}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource With}"/>
</Grid>
The resulting ItemsControl will display sorted items.
Finally i solved the sorting problem.
I am binding segments(observablecollection< seg>) to the itemscontrol. Previously In code behind i was generating segments directly like below
segments[0].name="GHI";
segments[0].age=40;
segments[1].name="ABC";
segments[1].age=20;
segments[2].name="DEF";
segments[2].age=30;
Instead of generating segments directly, i created another variable objSegments and generated the values like below.
objSegments[0].name="GHI";
objSegments[0].age=40;
objSegments[1].name="ABC";
objSegments[1].age=20;
objSegments[2].name="DEF";
objSegments[2].age=30;
After generating all the values, sorting is done and assigned to segments using the code below.
ObservableCollection<seg> sortedSegments = new ObservableCollection<seg>(objSegments.OrderBy(c => c.id));
foreach (var objSeg in sortedSegments)
{
segments.Add(objSeg);
}
It worked fine for me.

Collapse opened expanders in `Datatemplate`, when we open new one

My view works like this. I have an Observable Collection, which contains objects put on the list. By clicking on any item, I can open an expander related to that item. Here is the question: How can I
collapse (close) the previously opened expander when I open another one? I don't want to have a situation where multiple expanders are opened at the same time.
My WPF code looks like this:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<controls:Pivot>
<controls:PivotItem>
<ListBox x:Name="PizzaList" SelectionChanged="PizzaList_SelectionChanged" IsSynchronizedWithCurrentItem="false" >
<ListBox.ItemTemplate>
<DataTemplate x:Name="template">
<toolkit:ExpanderView Header="{Binding Name}" x:Name="expander" Style="{StaticResource ExpanderViewStyle}">
<toolkit:ExpanderView.Items>
<!--first stack panel would contain all elements which would be showed
after clicking on listbox item-->
<StackPanel Margin="20,0,0,0" Orientation="Vertical">
<!-- here is content of expander-->
</StackPanel>
</toolkit:ExpanderView.Items>
<toolkit:ExpanderView.Expander>
<TextBlock Text="{Binding Name_of_ingredients}" Width="500"></TextBlock>
</toolkit:ExpanderView.Expander>
</toolkit:ExpanderView>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PivotItem>
</controls:Pivot>
</Grid>
I could have a static number of expanders if I were working with a fixed dataset, but when the expander is in a data template, the number of items could change. I'm not sure how to solve this.
I think this can help:
private void PizzaListSelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var item in PizzaList.Items)
{
var listBoxItem =
PizzaList.ItemContainerGenerator.ContainerFromItem(item) as ListBoxItem;
var itemExpander = (Expander) GetExpander(listBoxItem);
if (itemExpander != null)
itemExpander.IsExpanded = false;
}
}
and search of Expander
private static DependencyObject GetExpander(DependencyObject container)
{
if (container is Expander) return container;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(container); i++)
{
var child = VisualTreeHelper.GetChild(container, i);
var result = GetExpander(child);
if (result != null)
{
return result;
}
}
return null;
}
More ways to find controls in this question
How can I find WPF controls by name or type?
I hope this looks ok, haven't posted code before.
I found this solution to expanding/collapsing listboxitems. The grid below is what holds my content. Its height is the contents actual height within the stackpanel (x:Name="stack") and stored in the Grid's Tag property. the DataTrigger animates the Tag. The custom:XSButton comes from Andy L's CustomControls, and acts as a ToggleButton. Just need this converter for the Grid height.
public class MultiplyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double result = 1.0;
for (int i = 0; i < values.Length; i++)
{
if (values[i] is double)
result *= (double)values[i];
}
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new Exception("Not implemented");
}
}
The Grid
<Grid x:Name="Grid" Grid.ColumnSpan="3" Grid.Row="1" >
<Grid.Tag>
<System:Double>0.0</System:Double>
</Grid.Tag>
<Grid.Height>
<MultiBinding Converter="{StaticResource multiplyConverter}">
<Binding Path="ActualHeight" ElementName="stack"/>
<Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</Grid.Height>
<custom:XSButton IsChecked="{Binding
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBoxItem}},
Path=IsSelected}" Background="Transparent" BorderThickness="0" BorderBrush="Transparent"
ClickMode="Press"
Foreground="Transparent" Style="{DynamicResource XSButtonStyle1}" >
<StackPanel x:Name="stack">
<TextBlock Text="{Binding Text}" FontSize="13.333" TextWrapping="Wrap" Foreground="#FFC0C7C8"/>
<TextBlock Text="{Binding Status_Message}" FontSize="13.333" TextWrapping="Wrap" Foreground="#FFC0C7C8"/>
</StackPanel>
</custom:XSButton>
</Grid>
the DataTrigger for expanding/collapsing
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Grid" Storyboard.TargetProperty="Tag" To="1" Duration="0:0:0.4"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Grid" Storyboard.TargetProperty="Tag" To="0" Duration="0:0:0.4"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</DataTemplate.Triggers>

Resources