Horizontal stretch list item in WP7 with grouping - silverlight

I want to implement ListBox Grouping on WP7. I found this article is very useful. Actually I made the grouping works. But I got a problem with ListItem horizontal stretch. I guess I need to set ItemContainerStyle and change HorizontalContentAlignment as Stretch. But it doesn't work for this case (if set the ItemTemplate directly, it works). Any suggestions? Thanks a lot!
Here is the code, the ListItem is supposed to be stretched, but it's centered instead.
C#:
public class GroupingItemsControlConverter : IValueConverter
{
public object Convert(object value, Type tagetType, object parameter, CultureInfo culture)
{
var valueAsIEnumerable = value as IEnumerable;
if (null == valueAsIEnumerable)
{
throw new ArgumentException("GroupingItemsControlConverter works for only IEnumerable inputs.", "value");
}
var parameterAsGroupingItemsControlConverterParameter = parameter as GroupingItemsControlConverterParameters;
if (null == parameterAsGroupingItemsControlConverterParameter)
{
throw new ArgumentException("Missing required GroupingItemsControlConverterParameter.", "parameter");
}
var groupSelectorAsIGroupingItemsControlConverterSelector = parameterAsGroupingItemsControlConverterParameter.GroupSelector as IGroupingItemsControlConverterSelector;
if (null == groupSelectorAsIGroupingItemsControlConverterSelector)
{
throw new ArgumentException("GroupingItemsControlConverterParameter.GroupSelector must be non-null and implement IGroupingItemsControlConverterSelector.", "parameter");
}
// Return the grouped results
return ConvertAndGroupSequence(valueAsIEnumerable.Cast<object>(), parameterAsGroupingItemsControlConverterParameter);
}
private IEnumerable<object> ConvertAndGroupSequence(IEnumerable<object> sequence, GroupingItemsControlConverterParameters parameters)
{
// Validate parameters
var groupKeySelector = ((IGroupingItemsControlConverterSelector)(parameters.GroupSelector)).GetGroupKeySelector();
var orderKeySelector = ((IGroupingItemsControlConverterSelector)(parameters.GroupSelector)).GetOrderKeySelector();
if (null == groupKeySelector)
{
throw new NotSupportedException("IGroupingItemsControlConverterSelector.GetGroupSelector must return a non-null value.");
}
// Do the grouping and ordering
var groupedOrderedSequence = sequence.GroupBy(groupKeySelector).OrderBy(orderKeySelector);
// Return the wrapped results
foreach (var group in groupedOrderedSequence)
{
yield return new ContentControl { Content = group.Key, ContentTemplate = parameters.GroupStyle };
foreach (var item in group)
{
yield return new ContentControl { Content = item, ContentTemplate = parameters.ItemStyle };
}
}
}
public object ConvertBack(object value, Type tagetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException("GroupingItemsControlConverter does not support ConvertBack.");
}
}
public class GroupingItemsControlConverterParameters
{
public DataTemplate GroupStyle { get; set; }
public DataTemplate ItemStyle { get; set; }
public IGroupingItemsControlConverterSelector GroupSelector { get; set; }
};
public abstract class IGroupingItemsControlConverterSelector
{
public abstract Func<object, IComparable> GetGroupKeySelector();
public virtual Func<IGrouping<IComparable, object>, IComparable> GetOrderKeySelector() { return g => g.Key; }
}
public class GroupingItemsControlConverterSelector : IGroupingItemsControlConverterSelector
{
public override Func<object, IComparable> GetGroupKeySelector()
{
return (o) => (o as ItemViewModel).Group;
}
}
XAML:
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.Resources>
<DataTemplate x:Key="GroupHeaderTemplate">
<Border BorderBrush="Yellow" BorderThickness="1" Margin="12,3,12,12" Padding="6" VerticalAlignment="Center">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Number}" HorizontalAlignment="Left" Margin="6,0,0,0" FontSize="22" Foreground="White"/>
<TextBlock Grid.Column="1" Text="{Binding Name}" HorizontalAlignment="Right" Margin="0,0,6,0" FontSize="22" Foreground="White"/>
</Grid>
</Border>
</DataTemplate>
<DataTemplate x:Key="CustomItemTemplate">
<Grid Margin="12,3,12,12" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="{Binding Number}" HorizontalAlignment="Left" Margin="6,0,0,0" FontSize="22" Foreground="White"/>
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding Name}" HorizontalAlignment="Right" Margin="0,0,6,0" FontSize="22" Foreground="White"/>
</Grid>
</DataTemplate>
<local:GroupingItemsControlConverter x:Key="GroupingItemsConverter" />
<local:GroupingItemsControlConverterSelector x:Key="GroupingItemsSelector" />
<local:GroupingItemsControlConverterParameters x:Key="GroupingItemParameters"
GroupStyle="{StaticResource GroupHeaderTemplate}"
ItemStyle="{StaticResource CustomItemTemplate}"
GroupSelector="{StaticResource GroupingItemsSelector}"
/>
<Style TargetType="ListBoxItem" x:Key="CustomItemContainerStyle">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</Grid.Resources>
<ListBox x:Name="TheListBox"
ItemsSource="{Binding Items, Converter={StaticResource GroupingItemsConverter}, ConverterParameter={StaticResource GroupingItemParameters}}"
ItemContainerStyle="{StaticResource CustomItemContainerStyle}" />
</Grid>

ListBox grouping? You should consider using the LongListSelector from the Silverlight Toolkit. And to simplify the binding for that, you can use the LongListCollection collection type (Check the entire example, for details).
Then you can simply create apps that groups values, for example like this:

Related

WPF EventHandler for TextBox.TextChanged in XAML or code behind?

Description
In WPF, using MvvmLight, I have a viewModel with an integer property SelectedIndex. Changing the value of this property is an expensive operation, so I only want to update the property if the operator is fairly certain that he finished typing.
I have a TextBox and a button. The operator types a number, and presses the button. This should lead to a command that updates the property.
Standard WPF MvvmLight solution for this
class MyViewModel
{
private int selectedIndex;
public MyViewModel()
{
this.CommandSelectIndex = new RelayCommand(ExecuteSelectIndex, CanSelectIndex);
}
public public RelayCommand<int> CommandSelectIndex { get; }
public int SelectedIndex
{
get => this.selectedIndex;
set => base.Set(nameof(SelectedIndex), ref this.selectedIndex, value);
}
private bool CanSelectIndex(int proposedIndex)
{
return proposedIndex > 0 && proposedIndex < MyData.Count;
}
private void ExecuteSelectIndex(int proposedIndex)
{
this.SelectedIndex = proposedIndex;
ProcessSelectedIndex(proposedIndex); // Expensive!
}
}
For those who know MvvmLight, this is fairly straightforward.
So while the operator is typing a number, I only want to update the button. I don't want to do anything with the intermediate values:
1 --> 12 --> 123 --> (typing error, backspace) --> 124 [press button]
XAML
<StackPanel Name="Test1" Orientation="Horizontal">
<TextBox Name="ProposedValue1" Text="1234" Width="300" Height="20"/>
<Button x:Name="ButtonChangeText1" Content="Change"
Height="30" Width="74" Padding="5,2"
Command="{Binding Path=CommandSelectedIndex}"
CommandParameter="{Binding ElementName=ProposedValue1, Path=Text}"/>
</StackPanel>
This works partly: at startup CanSelectIndex(1234) is called; If the button is pressed ExecuteSelectedIndex(1234) is called.
Problem
However, if the text of the TextBox changes, CanSelectIndex is not called.
The reason is because event ICommand.CanExecuteChanged is not raised when the textbox changes.
Solution:
Add an event handler:
XAML:
<TextBox Name="ProposedValue1" Text="1234" Width="300" Height="20"
TextChanged="textChangedEventHandler"/>
Code behind:
private void textChangedEventHandler(object sender, TextChangedEventArgs args)
{
((MyViewModel)this.DataContext).CommandSelectedIndex.RaiseCanExecuteChanged();
}
I always feel a bit uneasy whenever I have to write code behind. Is it standard to write eventhandlers in code behind, or is that a simplification that I only see in tutorials.
Is there a method that I can do this in XAML? Something with Binding?
TextChanged="TextChanged="{Binding Path=CommandSelectIndex ??? RaiseCanExecuteChanged() }
The RelayCommand class in MvvmLight has two implementations.
In the GalaSoft.MvvmLight.Command namespace and in the GalaSoft.MvvmLight.CommandWpf namespace.
You've probably used from namespace GalaSoft.MvvmLight.Command.
And this type doesn't actually update the state of the command.
If used from the GalaSoft.MvvmLight.CommandWpf namespace, then the state of the command is updated according to the predetermined logic.
Is there a method that I can do this in XAML? Something with Binding?
Just bind the Text property of the TextBox to a string source property of the view model and raise call the RaiseCanExecuteChanged method of the command from the setter of this one.
If you really want to handle an actual event for some reason, you should look into interaction triggers.
#Harald Coppulse, you are absolutely right!
Here is my test code for MvvmLight.
ViewModel:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
namespace InvalidateCommandMvvmLight.ViewModel
{
public class MyViewModel : ViewModelBase
{
private string _text;
private int _number;
public string Text { get => _text; private set => Set(ref _text, value); }
public int Number { get => _number; set => Set(ref _number, value); }
public RelayCommand<string> CommandTest { get; }
public RelayCommand<int> CommandNumber { get; }
public MyViewModel()
{
CommandTest = new RelayCommand<string>(Test, CanTest);
CommandNumber = new RelayCommand<int>(IntTest, CanIntTest);
}
private bool CanTest(string text)
{
// the text must have a minimum length of 4
// and be different from the current one
return text != null && text.Length >= 4 && text != Text;
}
private void Test(string text)
{
Text = text;
}
private bool CanIntTest(int num)
{
// The "num" parameter must be positive, less than 100
// and is not equal to the Number property
return num > 0 && num <100 && num != Number;
}
private void IntTest(int num)
{
Number = num;
}
}
}
XAML:
<Window x:Class="InvalidateCommandMvvmLight.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:InvalidateCommandMvvmLight"
xmlns:vm="clr-namespace:InvalidateCommandMvvmLight.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MyViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox x:Name="tbText"
Text="Alle eendjes zwemmen in het water" VerticalAlignment="Center"
/>
<Button Content="Change Text"
Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandTest}"
CommandParameter="{Binding ElementName=tbText, Path=Text}"/>
<TextBox Text="{Binding Text, Mode=OneWay}" Grid.Column="2" IsReadOnly="True" VerticalAlignment="Center"/>
<TextBox x:Name="tbNumber"
Grid.Row="1"
Text="55" VerticalAlignment="Center"/>
<Button Content="Change Number"
Grid.Row="1" Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandNumber}"
CommandParameter="{Binding ElementName=tbNumber, Path=Text}"/>
<TextBox Text="{Binding Number, Mode=OneWay}" IsReadOnly="True"
Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"/>
</Grid>
</Window>
Unfortunately, the CommandsWpf.RelayCommand class in MvvmLight is implemented not correctly.
It does not take into account the peculiarities of working with values ​​of different types in WPF.
To work in a typical for WPF way, an implementation should have something like this:
using System.ComponentModel;
namespace Common
{
#region Delegates for WPF Command Methods
/// <summary>Delegate of the executive team method.</summary>
/// <param name="parameter">Command parameter.</param>
public delegate void ExecuteHandler<T>(T parameter);
/// <summary>Command сan execute method delegate.</summary>
/// <param name="parameter">Command parameter.</param>
/// <returns><see langword="true"/> if command execution is allowed.</returns>
public delegate bool CanExecuteHandler<T>(T parameter);
#endregion
/// <summary>Class for typed parameter commands.</summary>
public class RelayCommand<T> : RelayCommand
{
/// <summary>Command constructor.</summary>
/// <param name="execute">Executable command method.</param>
/// <param name="canExecute">Method allowing command execution.</param>
public RelayCommand(ExecuteHandler<T> execute, CanExecuteHandler<T> canExecute = null)
: base
(
p => execute(TypeDescriptor.GetConverter(typeof(T)).IsValid(p) ? (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(p) : default),
p => (canExecute == null) || (TypeDescriptor.GetConverter(typeof(T)).IsValid(p) && canExecute((T)TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(p)))
)
{}
}
}
Unless you have the ability to change the RelayCommand implementation, you need to somehow use Binding's ability to auto-convert values.
One variant.
Create a property of the desired type in the ViewModel and use it as a proxy for autoconversion.
But if a non-numeric value is entered, then the command will not be able to define it.
You also need to check Validation.HasError.
ViewModel:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
namespace InvalidateCommandMvvmLight.ViewModel
{
public class MyViewModel : ViewModelBase
{
private string _text;
private int _number;
private int _numberView;
public string Text { get => _text; private set => Set(ref _text, value); }
public int Number { get => _number; set => Set(ref _number, value); }
public int NumberView { get => _numberView; set => Set(ref _numberView, value); }
public RelayCommand<string> CommandTest { get; }
public RelayCommand<int> CommandNumber { get; }
public MyViewModel()
{
CommandTest = new RelayCommand<string>(Test, CanTest);
CommandNumber = new RelayCommand<int>(IntTest, CanIntTest);
}
private bool CanTest(string text)
{
// the text must have a minimum length of 4
// and be different from the current one
return text != null && text.Length >= 4 && text != Text;
}
private void Test(string text)
{
Text = text;
}
private bool CanIntTest(int num)
{
// The "num" parameter must be positive, less than 100
// and is not equal to the Number property
return num > 0 && num <100 && num != Number;
}
private void IntTest(int num)
{
Number = num;
}
}
}
XAML:
<Window x:Class="InvalidateCommandMvvmLight.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:InvalidateCommandMvvmLight"
xmlns:vm="clr-namespace:InvalidateCommandMvvmLight.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MyViewModel NumberView="55"/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox x:Name="tbText"
Text="Alle eendjes zwemmen in het water" VerticalAlignment="Center"
/>
<Button Content="Change Text"
Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandTest}"
CommandParameter="{Binding ElementName=tbText, Path=Text}"/>
<TextBox Text="{Binding Text, Mode=OneWay}" Grid.Column="2" IsReadOnly="True" VerticalAlignment="Center"/>
<TextBox x:Name="tbNumber"
Grid.Row="1"
Text="{Binding NumberView, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center"/>
<Button Content="Change Number"
Grid.Row="1" Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandNumber}"
CommandParameter="{Binding NumberView}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=tbNumber}"
Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<TextBox Text="{Binding Number, Mode=OneWay}" IsReadOnly="True"
Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"/>
</Grid>
</Window>
Second variant.
Create an explicit proxy converter.
Converter:
using System;
using System.ComponentModel;
using System.Windows;
namespace InvalidateCommandMvvmLight
{
public class ProxyBinding : Freezable
{
public Type Type
{
get { return (Type)GetValue(TypeProperty); }
set { SetValue(TypeProperty, value); }
}
// Using a DependencyProperty as the backing store for Type. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TypeProperty =
DependencyProperty.Register(nameof(Type), typeof(Type), typeof(ProxyBinding), new PropertyMetadata(typeof(object), ChangedValueOrType));
private static void ChangedValueOrType(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ProxyBinding proxy = (ProxyBinding)d;
if (proxy.Type == null)
{
proxy.Value = null;
return;
}
if (proxy.Source == null)
return;
if (proxy.Type == proxy.Source.GetType())
return;
if (TypeDescriptor.GetConverter(proxy.Type).IsValid(proxy.Source))
proxy.Value = TypeDescriptor.GetConverter(proxy.Type).ConvertFrom(proxy.Source);
else
proxy.Value = null;
}
public object Source
{
get { return GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register(nameof(Source), typeof(object), typeof(ProxyBinding), new PropertyMetadata(null, ChangedValueOrType));
public object Value
{
get { return GetValue(ValueProperty); }
protected set { SetValue(ValuePropertyKey, value); }
}
// Using a DependencyProperty as the backing store for readonly Value. This enables animation, styling, binding, etc...
protected static readonly DependencyPropertyKey ValuePropertyKey =
DependencyProperty.RegisterReadOnly(nameof(Value), typeof(object), typeof(ProxyBinding), new PropertyMetadata(null));
public static readonly DependencyProperty ValueProperty = ValuePropertyKey.DependencyProperty;
protected override Freezable CreateInstanceCore()
{
return new ProxyBinding();
}
}
}
XAML:
<Window x:Class="InvalidateCommandMvvmLight.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:InvalidateCommandMvvmLight"
xmlns:vm="clr-namespace:InvalidateCommandMvvmLight.ViewModel"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MyViewModel/>
</Window.DataContext>
<Window.Resources>
<local:ProxyBinding x:Key="ProxyInt"
Type="{x:Type sys:Int32}"
Source="{Binding ElementName=tbNumber, Path=Text, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox x:Name="tbText"
Text="Alle eendjes zwemmen in het water" VerticalAlignment="Center"
/>
<Button Content="Change Text"
Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandTest}"
CommandParameter="{Binding ElementName=tbText, Path=Text}"/>
<TextBox Text="{Binding Text, Mode=OneWay}" Grid.Column="2" IsReadOnly="True" VerticalAlignment="Center"/>
<TextBox x:Name="tbNumber"
Grid.Row="1"
Text="55" VerticalAlignment="Center"/>
<Button Content="Change Number"
Grid.Row="1" Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandNumber}"
CommandParameter="{Binding Value, Source={StaticResource ProxyInt}}">
</Button>
<TextBox Text="{Binding Number, Mode=OneWay}" IsReadOnly="True"
Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"/>
<TextBlock Grid.Row="2" Text="{Binding Value,Source={StaticResource proxy}}"/>
</Grid>
</Window>
Another variant.
Create converter for bindings:
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;
namespace InvalidateCommandMvvmLight
{
public class ValueTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter is Type type && TypeDescriptor.GetConverter(type).IsValid(value))
return TypeDescriptor.GetConverter(type).ConvertFrom(value);
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
XAML:
<Window x:Class="InvalidateCommandMvvmLight.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:InvalidateCommandMvvmLight"
xmlns:vm="clr-namespace:InvalidateCommandMvvmLight.ViewModel"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MyViewModel/>
</Window.DataContext>
<Window.Resources>
<local:ValueTypeConverter x:Key="ValueTypeConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox x:Name="tbText"
Text="Alle eendjes zwemmen in het water" VerticalAlignment="Center"
/>
<Button Content="Change Text"
Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandTest}"
CommandParameter="{Binding ElementName=tbText, Path=Text}"/>
<TextBox Text="{Binding Text, Mode=OneWay}" Grid.Column="2" IsReadOnly="True" VerticalAlignment="Center"/>
<TextBox x:Name="tbNumber"
Grid.Row="1"
Text="55" VerticalAlignment="Center"/>
<Button Content="Change Number"
Grid.Row="1" Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandNumber}"
CommandParameter="{Binding Text, Converter={StaticResource ValueTypeConverter}, ConverterParameter={x:Type sys:Int32}, ElementName=tbNumber}">
</Button>
<TextBox Text="{Binding Number, Mode=OneWay}" IsReadOnly="True"
Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"/>
</Grid>
</Window>

WPF, RichTextBox, SelectionBrush, not SelectionBackColor

In a WPF app RichTextBox, I'm trying to find a way to provide a background color for various words within the box. In the System.Windows.Forms version of the RichTextBox, there was a very simple way to do this:
richTextBox1.SelectionBackColor = color;
richTextBox1.AppendText(word);
However, the System.Windows.Controls version of RichTextBox only has SelectionBrush, and this same method does not work.
Is a background color for different words in the RichTextBox possible?
You can work with the FlowDocument within the RichTextBox
<RichTextBox>
<RichTextBox.Document>
<FlowDocument>
<Paragraph>
<Run Background="Red">Hello World</Run>
<LineBreak/>
<Run Background="Green">This is a colored</Run>
<Run>text.</Run>
</Paragraph>
</FlowDocument>
</RichTextBox.Document>
</RichTextBox>
Edit regarding your comments: a (nearly) full example showing two different approaches.
No matter what you end up doing to present your text in the UI, you should have some sort of logic that creates a suitable data model of your highlighted text. The following example uses a collection of TextFragment where each fragment is optionally marked as highlighted.
public class TextFragment
{
public TextFragment(string text, bool isHighlighted)
{
this.Text = text;
this.IsHighlighted = isHighlighted;
}
public string Text { get; private set; }
public bool IsHighlighted { get; private set; }
}
Also, for the sample I use a class TextEntry to manage original text, search text and the resulting text fragments. Note I inherit from a BaseViewModel class which implements some helper functions for INotifyPropertyChanged related things. The helper function bool SetProperty<T>(ref T store, T value, [CallerMemberName]string propertyName = null) will check whether value and store are equal, potentially update the store with value and raise a property changed notification. The return value indicates, whether the value was really different/changed.
public class TextEntry : BaseViewModel
{
public TextEntry()
{
TextParts = new ObservableCollection<TextFragment>();
}
private void UpdateTextParts()
{
TextParts.Clear();
if (string.IsNullOrEmpty(SearchText))
{
TextParts.Add(new TextFragment(OriginalText, false));
return;
}
int startAt = 0;
do
{
int next = OriginalText.IndexOf(SearchText, startAt, StringComparison.CurrentCultureIgnoreCase);
if (next == -1)
{
TextParts.Add(new TextFragment(OriginalText.Substring(startAt), false));
return;
}
else
{
if (next != startAt)
{
TextParts.Add(new TextFragment(OriginalText.Substring(startAt, next - startAt), false));
}
// add highlighted part
TextParts.Add(new TextFragment(OriginalText.Substring(next, SearchText.Length), true));
startAt = next + SearchText.Length;
}
} while (startAt < OriginalText.Length);
}
private string _OriginalText;
public string OriginalText
{
get { return _OriginalText; }
set
{
if (SetProperty(ref _OriginalText, value))
{
UpdateTextParts();
}
}
}
private string _SearchText;
public string SearchText
{
get { return _SearchText; }
set
{
if (SetProperty(ref _SearchText, value))
{
UpdateTextParts();
}
}
}
public ObservableCollection<TextFragment> TextParts { get; private set; }
}
You can create a multi-part text in the UI by appending multiple textblocks with different text settings in a horizontal StackPanel. This way, the text parts can be managed by an ItemsControl. Alternatively, you can use the RichTextBox with its Document property, but this needs some more handling in code behind.
Some initialization code in the main window and a method to update the document for the RichTextBox example:
public MainWindow()
{
InitializeComponent();
var vm = new TextEntry();
grid1.DataContext = vm;
// this trigger works, but don't ask about efficiency for a bigger application
vm.TextParts.CollectionChanged += TextParts_CollectionChanged;
}
void TextParts_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ObservableCollection<TextFragment> data = sender as ObservableCollection<TextFragment>;
var doc = richTextBox1.Document;
var paragraph = new Paragraph();
paragraph.Inlines.AddRange(data.Select(x =>
{
var run = new Run(x.Text);
if (x.IsHighlighted)
{
run.Background = Brushes.LightCoral;
}
return run;
}));
doc.Blocks.Clear();
doc.Blocks.Add(paragraph);
}
And the XAML content of the window:
<Grid x:Name="grid1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Original Text: " Margin="3"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Searched Word: " Margin="3"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Highlighted Text: " Margin="3"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Highlighted Text2: " Margin="3"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding OriginalText, UpdateSourceTrigger=PropertyChanged}" Margin="3"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding SearchText,UpdateSourceTrigger=PropertyChanged}" Margin="3"/>
<ItemsControl Grid.Row="2" Grid.Column="1" ItemsSource="{Binding TextParts}" Margin="3" IsTabStop="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsHighlighted}" Value="True">
<Setter Property="Background" Value="LightCoral"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<RichTextBox Grid.Row="3" Grid.Column="1" x:Name="richTextBox1" Margin="3" VerticalAlignment="Top" IsReadOnly="True"/>
</Grid>
Expected Program:
Two lines with text inputs. One for the original text, one for the searched text.
3rd line showing the original text with search highlights as TextBlocks.
4th line showing the original text with search highlights as RichTextBox.

How to databind to a ItemsControl

I have a SL5 project where I am trying to data bind a collection to a ItemsControl, I keep getting the error:
BindingExpression path error: 'ItemName' property not found on 'EventViewer.Data.ViewModels.ProductListModel' 'EventViewer.Data.ViewModels.ProductListModel' (HashCode=7414170). BindingExpression: Path='ItemName' DataItem='EventViewer.Data.ViewModels.ProductListModel' (HashCode=7414170); target element is 'System.Windows.Controls.TextBlock' (Name=''); target property is 'Text' (type 'System.String')..
BindingExpression path error: 'Price' property not found on 'EventViewer.Data.ViewModels.ProductListModel' 'EventViewer.Data.ViewModels.ProductListModel' (HashCode=7414170). BindingExpression: Path='Price' DataItem='EventViewer.Data.ViewModels.ProductListModel' (HashCode=7414170); target element is 'System.Windows.Controls.TextBlock' (Name=''); target property is 'Text' (type 'System.String')..
BindingExpression path error: 'Description' property not found on 'EventViewer.Data.ViewModels.ProductListModel' 'EventViewer.Data.ViewModels.ProductListModel' (HashCode=7414170). BindingExpression: Path='Description' DataItem='EventViewer.Data.ViewModels.ProductListModel' (HashCode=7414170); target element is 'System.Windows.Controls.TextBlock' (Name=''); target property is 'Text' (type 'System.String')..
I have these properties in the ProductQtyItem and the databinding appears to be setup correctly. Here is what I have:
The XAML:
<sdk:ChildWindow
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:EventViewer="clr-namespace:EventViewer"
xmlns:ViewModels="clr-namespace:EventViewer.Data.ViewModels"
xmlns:converters="clr-namespace:EventViewer.Converters"
x:Class="EventViewer.PurchaseWindow"
Title="Purchase"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Width="539" Height="550">
<sdk:ChildWindow.Resources>
<converters:BooleanVisibilityConverter x:Key="BooleanVisibilityConverter"/>
<converters:NumericCurrencyConverter x:Key="NumericCurrencyConverter"/>
<ControlTemplate x:Key="ProductItemTemplate" TargetType="ItemsControl">
<Grid Margin="3">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" VerticalAlignment="Center"
Style="{StaticResource ProductNameTextBlockStyle}"
Text="{Binding ItemName, Mode=OneWay}"/>
<TextBlock Grid.Column="0" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Center"
Style="{StaticResource ProductPriceTextBlockStyle}"
Text="{Binding Price, Converter={StaticResource NumericCurrencyConverter}, Mode=OneWay}" />
<TextBlock Grid.Column="1" Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Center" TextWrapping="Wrap"
Text="{Binding Description, Mode=OneWay}"/>
</Grid>
</ControlTemplate>
</sdk:ChildWindow.Resources>
<Grid x:Name="LayoutRoot">
<Grid.DataContext>
<ViewModels:ProductListModel/>
</Grid.DataContext>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Viewbox Stretch="Uniform" StretchDirection="DownOnly" HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" Height="100"
DataContext="{Binding ImageData}">
<Image Source="{Binding ImagePath, Mode=OneWay}" ImageOpened="Image_ImageOpened"/>
</Viewbox>
<ItemsControl
HorizontalAlignment="Left" Margin="0,105,0,0" VerticalAlignment="Top" Width="521" Height="377"
Template="{StaticResource ProductItemTemplate}"
ItemsSource="{Binding ProductQtyItems}"/>
<Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Grid.Row="1" />
</Grid>
</sdk:ChildWindow>
The code that is to follow is how I wire up a ProductListModel to the LayoutRoot.DataContext of the above XAML code. So, if I understand things correctly, the binding for the ItemsControl will bind to the List ProductQtyItems of ProductListModel. So, won't the templates have the datasource set to ProductQtyItem? What am I missing?
Here is the rest of the code:
And this is the code that populates the data binding:
public static void Display(FrameworkElement parent, ImageData imageData)
{
var purchaseWindow = new PurchaseWindow();
var productQtyItems = new List<ProductQtyItem>();
foreach (var p in SystemSettings.GetInstance().ProductList.Collection)
{
int qty = 0;
imageData.ProductListItemQty.TryGetValue(p.Id, out qty);
productQtyItems.Add(ProductQtyItem.Create(p, qty));
}
purchaseWindow.LayoutRoot.DataContext = new ProductListModel
{
ImageData = imageData,
ProductQtyItems = productQtyItems
};
purchaseWindow.Show();
}
Here is the Model that is being set to the LayoutRoot.Datacontext:
public class ProductListModel : PropertyChangedBase
{
private List<ProductQtyItem> _productQtyItems;
private ImageData _imageData;
public List<ProductQtyItem> ProductQtyItems
{
get { return _productQtyItems; }
set { _productQtyItems = value; }
}
public ImageData ImageData
{
get { return _imageData; }
set { _imageData = value; }
}
}
And finally the ProductQtyItem:
public class ProductQtyItem : PropertyChangedBase
{
public static ProductQtyItem Create(ProductItem productItem, int qty)
{
return new ProductQtyItem
{
_productItem = productItem,
_qty = qty,
};
}
private ProductItem _productItem;
private int _qty;
public int Id { get { return _productItem.Id; } }
public int SortOrder { get { return _productItem.SortOrder; } }
public string ItemName { get { return _productItem.ItemName; } }
public string Description { get { return _productItem.Description; } }
public double Price { get { return _productItem.Price; } }
public bool IsQtyEnabled { get { return _productItem.IsQtyEnabled; } }
public int Qty
{
get { return _qty; }
set
{
if (value != _qty)
{
_qty = value;
NotifyPropertyChanged("Qty");
}
}
}
public override bool Equals(object obj)
{
var other = obj as ProductQtyItem;
if (other == null)
return false;
return Id == other.Id;
}
public override int GetHashCode()
{
return Id;
}
}
The error message indicates that DataContext of controls that bound to properties ItemName, Price, Description is ProductListModel instead of ProductQtyItem.
Which model/viewmodel has property named Collection and of what type is Collection? I think the problem is here, if it is of type ProductListModel.
UPDATE :
Responding to your update, try to change resource definition from ControlTemplate :
<ControlTemplate x:Key="ProductItemTemplate" TargetType="ItemsControl">..</ControlTemplate>
to DataTemplate :
<DataTemplate x:Key="ProductItemTemplate">..</DataTemplate>
then bind ItemsControl's ItemTemplate property instead of Template property :
<ItemsControl
HorizontalAlignment="Left" Margin="0,105,0,0" VerticalAlignment="Top" Width="521" Height="377"
ItemTemplate="{StaticResource ProductItemTemplate}"
ItemsSource="{Binding ProductQtyItems}"/>
PS : actually there are too many information posted, and still not directly reproduceable, because some classes definition still missing. Next time try to simplify the scenario using reduced viewmodel, remove unrelated resources (styles, converters), but make sure the problem still appears.

Binding does not work for TemplateSelector

I have one part of the view is dynamic based on a TemplateSelector. However, the binding does not work for the controls in the DataTemplate.(The controls do show on screen, just the conetent/texts are empty). I suspect it's a DataContext issue, but couldn't figure out after a lot of searching on line. Here is my XAML:
<Grid>
<Grid.DataContext>
<local:MyViewModel/>
</Grid.DataContext>
<Grid.Resources>
<DataTemplate x:Key="T1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Grid.Row="0"
Text="Music"
Style="{StaticResource ResourceKey=textBlockStyle}" />
<TextBox Grid.Column="1"
Grid.Row="0"
Style="{StaticResource ResourceKey=textBoxStyle}"
Text="{Binding Path=MusicName}" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="T2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Grid.Row="0"
Text="Currency"
Style="{StaticResource ResourceKey=textBlockStyle}" />
<ComboBox Grid.Column="1"
Grid.Row="0"
Style="{StaticResource ResourceKey=comboBoxStyle}"
ItemsSource="{Binding Path=Currency_List}"
SelectedItem="{Binding Path=Currency}" />
</Grid>
</DataTemplate>
<local:ProductTypeTemplateSelector T1="{StaticResource ResourceKey=T1}"
T2="{StaticResource ResourceKey=T2}"
x:Key="myTemplateSelector" />
<Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
<RowDefinition Height="40"/>
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<!-- This biding works -->
<TextBlock Grid.Row="0"
Text="{Binding Path=MusicName}"/>
<!-- This biding does not work -->
<ContentControl Grid.Row="1"
Name="ccc"
Content="{Binding Path=Product_Type}"
ContentTemplateSelector="{StaticResource myTemplateSelector}">
</ContentControl>
</Grid>
This is my View Model (Technically, it is View Model and Model mixed together. I am not really implementing a full MVVM pattern)
public class MyViewModel: INotifyPropertyChanged
{
public MyViewModel()
{
SetLists();
}
protected void SetLists()
{
SetList_Product_Type();
SetList_Currency();
}
protected void SearchAndPopulate()
{
string query = string.Format("select * from dbo.vehicle_attributes where ticker like '%{0}%'", Search_Text);
DataView dv = DAL.ExecuteQuery(query);
if (dv.Count > 0)
{
DataRowView dvr = dv[0];
Vehicle_Id = int.Parse(dvr["vehicle_id"].ToString());
Product_Type = dvr["product_type_name"].ToString();
Vehicle_Name = dvr["vehicle_name"].ToString();
Is_Onshore = dvr["domicile_name"].ToString() == "Onshore";
Currency = dvr["currency"].ToString();
CUSIP = dvr["CUSIP"].ToString();
ISIN = dvr["isin"].ToString();
Ticker = dvr["ticker"].ToString();
Valoren = dvr["valoren"].ToString();
PC_Class = PC_Class_List.Find(x => x.Class_Name == dvr["class_name"].ToString());
Implementation_Type = Implementation_Type_List.Find ( x => x.Implementation_Type_Name == dvr["implementation_type_name"].ToString());
Price_Frequency = Price_Frequency_List.Find( x => x.Price_Frequency_Name == dvr["price_freq_name"].ToString());
Status = Status_List.Find( x => x.Status_Name == dvr["status_name"].ToString());
if (!string.IsNullOrEmpty(dvr["last_status_update"].ToString()))
{
Status_Date = DateTime.Parse(dvr["last_status_update"].ToString());
}
else
{
Status_Date = DateTime.MinValue;
}
switch (Product_Type)
{
case "Mutual Fund":
query = string.Format("select lf.dividend_currency, i.ticker from dbo.liquid_funds lf " +
"left join dbo.vehicles i on i.vehicle_id = lf.index_id " +
"where lf.vehicle_id ='{0}'",Vehicle_Id);
DataView dv_mutual_fund = DAL.ExecuteQuery(query);
if(dv_mutual_fund.Count > 0)
{
DataRowView dvr_mutual_fund = dv_mutual_fund[0];
Dividend_Currency = dvr_mutual_fund["dividend_currency"].ToString();
Benchmark_Ticker = dvr_mutual_fund["ticker"].ToString();
}
break;
default:
break;
}
}
}
public ICommand SearchVehicleCommand
{
get
{
return new Command.DelegateCommand(SearchAndPopulate);
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
//ProductType
protected List<string> _product_Type_List = new List<string>();
public List<string> Product_Type_List
{
get
{
return _product_Type_List;
}
set
{
_product_Type_List = value;
OnPropertyChanged("Product_Type_List");
}
}
protected void SetList_Product_Type()
{
string query = "SELECT * FROM dbo.product_types WHERE is_enabled = 1";
DataView dv = DAL.ExecuteQuery(query);
List<string> l = new List<string>();
for (int i = 0; i < dv.Count; i++)
{
l.Add(dv[i]["product_type_name"].ToString());
}
Product_Type_List = l;
}
protected string _product_type;
public string Product_Type
{
get
{
return _product_type;
}
set
{
_product_type = value;
OnPropertyChanged("Product_Type");
SetList_Implementation_Type();
}
}
//Currency
protected List<string> _currency_List = new List<string>();
public List<string> Currency_List
{
get
{
return _currency_List;
}
set
{
_currency_List = value;
OnPropertyChanged("Currency_List");
}
}
protected void SetList_Currency()
{
string query = "SELECT currency FROM dbo.currencies";
DataView dv = DAL.ExecuteQuery(query);
List<string> l = new List<string>();
for (int i = 0; i < dv.Count; i++)
{
l.Add(dv[i]["currency"].ToString());
}
Currency_List = l;
}
protected string _currency;
public string Currency
{
get
{
return _currency;
}
set
{
_currency = value;
OnPropertyChanged("Currency");
}
}
// Music Name
protected string _musicName;
public string MusicName
{
get
{
return _musicName;
}
set
{
_musicName = value;
OnPropertyChanged("MusicName");
}
}
}
This is the class interface (sorry for the formatting above, but somehow I can't get it right):
And this is my DelegateCommand class:
public class DelegateCommand : ICommand
{
private readonly Action _action;
public DelegateCommand(Action action)
{
_action = action;
}
public void Execute(object parameter)
{
_action();
}
public bool CanExecute(object parameter)
{
return true;
}
}
This is the DataTemplateSelector:
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate T1 { get; set; }
public DataTemplate T2 { get; set; }
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
string product_type = (string)item;
if (product_type == "Type1")
return T1;
else
return T2;
}
}
The DataContext of a DataTemplate is set to the object that it is bound to. So in your case the DataContext for your Templates are Product_Type and you are expecting it to be MyViewModel.
There is a workaround for what you need. It uses a RelativeSource binding and FindAncester to access the DataContext of the Parent object.
Your DataTemplate bindings should look like this:
XAML
<DataTemplate x:Key="T1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Grid.Row="0"
Text="Music"
Style="{StaticResource ResourceKey=textBlockStyle}" />
<TextBox Grid.Column="1"
Grid.Row="0"
Style="{StaticResource ResourceKey=textBoxStyle}"
Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentControl}}, Path=DataContext.MusicName}" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="T2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Grid.Row="0"
Text="Currency"
Style="{StaticResource ResourceKey=textBlockStyle}" />
<ComboBox Grid.Column="1"
Grid.Row="0"
Style="{StaticResource ResourceKey=comboBoxStyle}"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentControl}}, Path=DataContext.Currency_List}"
SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentControl}}, Path=DataContext.Currency}" />
</Grid>
</DataTemplate>
From MSDN
Find Ancestor - Refers to the ancestor in the parent chain of the data-bound element. You can use this to bind to an ancestor of a specific type or its subclasses.
The AncestorType attribute goes up the visual tree and finds the first Ancestor of the given type, in this case it's looking for ContentControl the path to the required property can then be set relative to this.
Update
Think of a template as a guide on how to display an object. The DataContext of the DataTemplate is going to be whatever object it is asked it to display.
In this case the ContentControl is told to display Product_Type and, depending on the value of Product_Type, to use a particular Template. Product_Type is given to the DataTemplate and becomes the DataContext.
WPFTutorials has some good examples.

WPF ListBoxItems problem

In wpf app I have binded Dictionary to listbox. I have own style on listbox.
Listbox item consit image and 3 texblocks.
Friend class is here:
public class FriendData
{
public string idUser { get; set; }
public string nick { get; set; }
public string sefNick { get; set; }
public string status { get; set; }
public string photo { get; set; }
public string sex { get; set; }
public string isFriend { get; set; }
public string blockQuote { get; set; }
public FriendData(string idUser, string nick, string sefNick, string status, string photo, string sex, string isFriend)
{
this.idUser = idUser;
this.nick = nick;
this.sefNick = sefNick;
this.status = status;
this.photo = photo;
this.sex = sex;
this.isFriend = isFriend;
}
}
ListBox style is here:
<Style x:Key="friendsListStyle" TargetType="{x:Type ListBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Grid Name="MainGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="60"></RowDefinition>
</Grid.RowDefinitions>
<Image Source="{Binding Value.photo}" Margin="4,4,4,2"/>
<Grid Name="SlaveGrid" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Name="tbName" Text="{Binding Value.nick}" Grid.Column="0" Grid.Row="0" Margin="2,2,2,2" FontSize="13" FontWeight="Medium"></TextBlock>
<TextBlock Name="tbBlockQuote" Text="{Binding Value.blockQuote}" Grid.Column="0" Grid.Row="1" Margin="2,2,2,2" FontSize="11" FontWeight="Normal" Foreground="DarkGray"></TextBlock>
<TextBlock Name="tbStatus" Text="{Binding Value.status}" Grid.Column="0" Grid.Row="2" Margin="2,2,2,2" FontSize="11" FontWeight="Normal"></TextBlock>
</Grid>
</Grid>
<DataTemplate.Triggers>
<!--<Trigger Property="IsMouseOver" Value="true">
<Setter Property="LayoutTransform" TargetName="MainGrid">
<Setter.Value>
<ScaleTransform ScaleX="1.35" ScaleY="1.35" />
</Setter.Value>
</Setter>
</Trigger>-->
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
My first problem is, tbStatus is binded to property status of class Friend. Status can have only 3 values : 0,1,2. If status have value 0, I need set tbStatus.Text="Offline", if status have value 1, I need set tbStatus.Text="Online" and if status is 2 then must be tbStatus.Text="Just Log". Its possible this condition set from XAML in listBox style?
<TextBlock Name="tbStatus" Text="{Binding Value.status}" Grid.Column="0" Grid.Row="2" Margin="2,2,2,2" FontSize="11" FontWeight="Normal"></TextBlock>
My second problem is if status is 0, I need convert Image to grayscale. I have func wich make converting to grayscale. I would like set these condition in listBox style. Any advance?
Here is :
private void ConvertImageToGrayScaleImage(string uri)
{
Image grayImage = new Image();
BitmapImage bmpImage = new BitmapImage();
bmpImage.BeginInit();
bmpImage.UriSource = new Uri(uri);
bmpImage.EndInit();
FormatConvertedBitmap grayBitmap = new FormatConvertedBitmap();
grayBitmap.BeginInit();
grayBitmap.Source = bmpImage;
grayBitmap.DestinationFormat = PixelFormats.Gray8;
grayBitmap.EndInit();
grayImage.Source = grayBitmap;
LayoutRoot.Children.Add(grayImage);
}
In both cases, your best bet is to write an IValueConverter.
For instance, here is one for converting your text:
public class StatusToTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int status = Int32.Parse(value.ToString());
switch (status)
{
case 0:
return "Offline";
case 1:
return "Online";
case 2:
return "Just Log";
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string status = value.ToString();
switch (status)
{
case "Offline":
return 0;
case "Online":
return 1;
case "Just Log":
return 2;
}
return Binding.DoNothing;
}
}
And your Text binding becomes (after adding the reference in an appropriate Resources section of your XAML):
<TextBlock Name="tbStatus"
Text="{Binding Value.status,
Converter={StaticResource statusToText}}"
Grid.Column="0"
Grid.Row="2"
Margin="2,2,2,2"
FontSize="11"
FontWeight="Normal"></TextBlock>
I'll leave the image converter to you, since this is basic stuff, and it would essentially be a repeat of this code.
Note that this is hard-coding values, which isn't really a Best Practice. You should probably be using enumerations, as well as resources (instead of hard-coded strings) if you ever plan to localize this app.

Resources