Change DataTemplate to use depending on condition - silverlight

I have 3 user controls
Control 1
Control 2
Control 3
I have a stack panel that contains an ItemsControl
<UserControl.Resources>
<DataTemplate x:Key="Template1">
<my:UserControl1 Height="117"/>
</DataTemplate>
<DataTemplate x:Key="Template2">
<my:UserControl3 Height="117"/>
</DataTemplate>
<DataTemplate x:Key="Template3">
<my:UserControl3 Height="117"/>
</DataTemplate>
</UserControl.Resources>
<StackPanel Name="stackPanel3" Orientation="Vertical" VerticalAlignment="Bottom" Width="Auto">
<ItemsControl ItemsSource="{Binding BlocksForMonth.Blocks}" ItemTemplate="{StaticResource Template1}">
</ItemsControl>
</StackPanel>
BlocksForMonths.Blocks is a list of view models. The Blocks class has a property called ClipType. If the clipType is 1, I want to use Template1. If its 2 I want to use Template 2. If its 3 I want to use Template 3
These templates contain different user controls
How can I do this through binding?
I have considered 1 template with the 3 controls, but I dont know how to bind the visibility?
In this XAML I am binding to a list not a single item
Paul

I would put the 3 controls in the same template and use Visibility to display the correct one. What I would do is build an IValueConverter to convert the deciding value (your case it's ClipType) and compare that to the ConverterParameter. If they are equal, return Visibility.Visible, otherwise return Visibility.Collapsed.
<UserControl.Resources>
<my:ClipTypeToVisibilityConverter x:Key="converter"/>
<DataTemplate x:Key="Template">
<StackPanel>
<my:UserControl1 Height="117" Visibility={Binding ClipType, Converter={StaticResource converter}, ConverterParameter=1} />
<my:UserControl2 Height="117" Visibility={Binding ClipType, Converter={StaticResource converter}, ConverterParameter=2} />
<my:UserControl3 Height="117" Visibility={Binding ClipType, Converter={StaticResource converter}, ConverterParameter=3} />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<StackPanel Name="stackPanel3" Orientation="Vertical" VerticalAlignment="Bottom" Width="Auto">
<ItemsControl ItemsSource="{Binding BlocksForMonth.Blocks}" ItemTemplate="{StaticResource Template}">
</ItemsControl>
</StackPanel>
This example assumes the ClipType property is on each item view model in the list being displayed.
Here is a C# example converter.
public class ClipTypeToVisibilityConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var clipType = value.ToString();
if (clipType == (string)parameter))
return Visibility.Visible;
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Sorry, everything was air-code. But I think you get the idea.

Related

ListView inside ListView + control.Visibility

I'm creating a questionnaire app. My way of doing this is to create a ListView which contains question text and another ListView which contains list af answers(as RadioButtons).
The problem came when there are question which have an answer "Others" which require a TextBox for user to type some text. How can I achieve this? I mean i want to make TextBox visible only when collection of answers contains RadioButton with content "Other".
Below is my xaml code for ListView.
<ListView SelectionChanged="myList_SelectionChanged" ItemsSource="{Binding OCquestions}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="20 0 20 0">
<TextBlock Text="{Binding Path=questionText}"/>
<ListView Name="ListaLista" SelectionChanged="myList_SelectionChanged" ItemsSource="{Binding Path=listOfAnswer}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<RadioButton GroupName="{Binding Path=questId}" Content="{Binding Path=answerText}" Checked="RadioButton_Checked"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
// HERE I WANT A TEXTBOX WHICH IS VISIBLE ONLY WHEN listOfAnswer collection contain a RadioButton with Content "Others"
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have no idea how to achieve this. I'm not familiar with Converters. Can anyone give me some tip ?
You need some Trigger to show/hide the TextBox, something like this:
<DataTemplate>
<StackPanel Orientation="Horizontal">
<RadioButton GroupName="{Binding Path=questId}"
Content="{Binding Path=answerText}"
Checked="RadioButton_Checked" Name="radio"/>
<TextBox Name="other" Visibility="Collapsed"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding answerText}" Value="Other">
<Setter TargetName="radio" Property="Content" Value=""/>
<Setter TargetName="other" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
You can see that the DataTrigger listens to answerText, if it's "Other" just set the Content of Radio to empty string and set the TextBox's Visibility to Visible to show it. This TextBox will be shown on the right of the RadioButton.
First add a ValueConverter:
public abstract class BaseConverter : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
public class AnswerCollectionToVisibilityConverter : BaseConverter, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ICollection<ListOfAnswers> answers = value as ICollection<ListOfAnswers>;
if (answers != null)
{
foreach (Answer answer in answers)
{
if (OtherRadioButtonIsHere)
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
Then add a TextBox that uses the ValueConverter to set the Visibility:
<TextBox Visibility="{Binding Path=listOfAnswer, Converter={AnswerCollectionToVisibilityConverter}}" />

WPF binding TextBox and ObservableDictionary<Int64,String> (display String by Id)

Each item of my Employee's list has Post property. This property is Int64 type. Also, I have some ObservableDictionary<Int64,String> as static property. Each Employe must display the String value by its key.
DataTemplate for Employe item (I deleted the superfluous):
<DataTemplate x:Key="tmpEmploye">
<Border BorderThickness="3" BorderBrush="Gray" CornerRadius="5">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=Post}"/>
</StackPanel>
</Border>
</DataTemplate>
But this code displayed the Int64 value, not the String. String for getting static dictionary:
"{Binding Source={x:Static app:Program.Data}, Path=Posts}"
I know how solve it problem for ComboBox, but I don't know for TextBlock. For ComboBox I wrote it (it is works fine):
<ComboBox x:Name="cboPost" x:FieldModifier="public" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Margin="2" Grid.ColumnSpan="2"
ItemsSource="{Binding Source={x:Static app:Program.Data}, Path=Posts}" DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding Path=Post, Mode=TwoWay}">
</ComboBox>
But how can I solve it for TextBlock?
mmmmm, I'm sure I have developed something for this scenario before but I can't remember or find anything related!
IMO you can use a converter, so you pass your Post (Int64) to the converter and it returns the string value from the dictionary, although it must be a better solution.
[ValueConversion(typeof(Int64), typeof(string))]
public class PostToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// validation code, etc
return (from p in YourStaticDictionary where p.Key == Convert.ToInt64(value) select p.Value).FirstOrDefault();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
}
}
XAML:
<Window ...
xmlns:l="clr-namespace:YourConverterNamespace"
...>
<Window.Resources>
<l:PostToStringConverter x:Key="converter" />
</Window.Resources>
<Grid>
<TextBlock Text="{Binding Post, Converter={StaticResource converter}}" />
</Grid>
</Window>

Binding a Silverlight TabControl to a complex object using a converter

I am trying to bind my data to a tab control. I have got the headers displaying fine but I'm not sure how I get the content of the tabs to bind correctly based on my item template shown below.
I think I'm missing something when I'm creating the tab item but I'm not sure how to bind my MyCustomObject to each of the TabItem's.
XAML:
<sdk:TabControl ItemsSource="{Binding Singles,Converter={StaticResource TabConverter}}">
<sdk:TabControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</sdk:TabControl.ItemsPanel>
<sdk:TabControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Converter={StaticResource RoundNumberConverter}}" Margin="2" />
<ListBox x:Name="Matches" ItemsSource="{Binding}" Margin="2">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Converter={StaticResource SeedingConverter}, ConverterParameter=true}" Margin="2" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding Converter={StaticResource SeedingConverter}, ConverterParameter=false}" Margin="2" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Player1Name}" Margin="2" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Player2Name}" Margin="2" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</sdk:TabControl.ItemTemplate>
</sdk:TabControl>
Converter:
public class TabConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
IEnumerable<IGrouping<string, MyCustomObject>> source = value as IEnumerable<IGrouping<string, MyCustomObject>>;
if (source != null)
{
var controlTemplate = (ControlTemplate)parameter;
List<TabItem> result = new List<TabItem>();
foreach (IGrouping<string, MyCustomObject> tab in source)
{
result.Add(new TabItem()
{
Header = tab.Key,
DataContext = tab //not sure this is right?
});
}
return result;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
You're most of the way there, the following works for me:
Strip out your DataTemplate and drop its contents straight into a new UserControl, for the example here, lets call it MatchesView.
Then in your TabConverter, amend the contents of the foreach loop to something like the following:
TabItem tabitem = new TabItem();
tabitem.Header = tab.Key;
MatchesView tabview = new MatchesView();
tabview.DataContext = parameter;
tabitem.Content = tabview;
result.Add(tabitem);
Note: this requires that you pass your ViewModel to the TabConverter as a parameter, eg:
<sdk:TabControl SelectedItem="{Binding YourSelectedObject}" ItemsSource="{Binding YourCollectionObject, Converter={StaticResource TabConverter}, ConverterParameter={StaticResource YourViewModel}, Mode=TwoWay}" />
Then, as you have your ViewModel in each instance of the new control, adjust your binding accordingly!
Note that the trick is that you have a separate binding for the single instance of the Selected object
checking your problem I think there is no support for that. The WPF's TabItem contains ItemTemplate & ContentTemplate. The first is the template for the Header, the second is the template for the "body". In silverlight we still have no ContentTemplate. Haven't seen an official statement yet, but this guy says it won't be supported till SL5.

WPF pass parent binding object to the converter

I have ItemsControl that is bound to collection of type Student.
Inside the ItemTemplate I have a TextBox that uses IValueConverter to do some custom calculations and logic. I want to pass the actual Student object to the value converter, instead a property of it. How can I do that? Here's a sample of my code.
<ItemsControl ItemsSource="{Binding StudentList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding ????, Converter={StaticResource MyConverter}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In the code I have this
public class MyValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// I want 'value' to be of type Student.
return null;
}
}
You can just leave out the path. That way you get at the actual object bound to.
<TextBlock Text="{Binding Converter={StaticResource MyConverter}}"/>
or if you want to be explicit about it:
<TextBlock Text="{Binding Path=., Converter={StaticResource MyConverter}}"/>

Show the validation error template on a different control in WPF

I have a UserControl that contains other controls and a TextBox. It has a Value property that is bound to the TextBox text and has ValidatesOnDataErrors set to True.
When a validation error occurs in the Value property binding, the error template (standard red border) is shown around the entire UserControl.
Is there a way to show it around the TextBox only?
I'd like to be able to use any error template so simply putting border around textbox and binding its color or something to Validation.HasError is not an option.
Here's my code:
<DataTemplate x:Key="TextFieldDataTemplate">
<c:TextField DisplayName="{Binding Name}" Value="{Binding Value, Mode=TwoWay, ValidatesOnDataErrors=True}"/>
</DataTemplate>
<controls:FieldBase x:Name="root">
<DockPanel DataContext="{Binding ElementName=root}">
<TextBlock Text="{Binding DisplayName}"/>
<TextBox x:Name="txtBox"
Text="{Binding Value, Mode=TwoWay, ValidatesOnDataErrors=True}"
IsReadOnly="{Binding IsReadOnly}"/>
</DockPanel>
UserControl (FieldBase) is than bound to ModelView which performs validation.
to accomplish this task I've used this solution. It uses converter, that "hides" border by converting (Validation.Errors).CurrentItem to Thickness.
<Grid>
<Grid.Resources>
<data:ValidationBorderConverter
x:Key="ValidationBorderConverter" />
</Grid.Resources>
<Border
BorderBrush="#ff0000"
BorderThickness="{Binding
ElementName=myControl,
Path=(Validation.Errors).CurrentItem,
onverter={StaticResource ValidationBorderConverter}}">
<TextBox
ToolTip="{Binding
ElementName=myControl,
Path=(Validation.Errors).CurrentItem.ErrorContent}" />
</Border>
</Grid>
ValidationBorderConverter class is pretty simple:
[ValueConversion(typeof(object), typeof(ValidationError))]
public sealed class ValidationBorderConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return (value == null) ? new Thickness(0) : new Thickness(1);
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

Resources