I want to render a couple of elements using an ItemsControl, and highlight one of them
My ViewModel:
public class ViewModel
{
public List<Item> Items;
public Item HighlightedItem;
}
My XAML:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<myUserControl Background="{?}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I want to highlight the item by setting the background property to something specific, how should I go on about it?
First have a converter which will compare reference of two objects say ObjectEqualsConverter
public class ObjectEqualsConverter : IMultiValueConverter
{
#region IMultiValueConverter
public object Convert(object[] values, Type targetType, object parameter,
CultureInfo culture)
{
return values[0] == values[1];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
And in XAML file, use converter to check if current item is same as highlighted item in ViewModel and in case converter returns true set the color of control using trigger-
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<myUserControl x:Name="myControl" />
<DataTemplate.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource ObjectEqualsConverter}">
<Binding/>
<Binding Path="DataContext.HighlightedItem" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ItemsControl}"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter TargetName="myControl" Property="Background" Value="Red"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Make sure you add converter as a resource in your xaml file.
Related
I have an wpf Expander with templated header. In this template, i have an TextBox, which use Binding with Converter to set MaxLines and MinLines, which depends on Expander.IsExpanded.
The idea is to let user see first line of text and show more when Expander is expanded (alternate solution would be to make that TextBox.Visiblitity = Collapsed when expanded and another TextBox.Visibility = Visible, but user will lost their cursor position, marked text and i dont know what else)
<UserControl>
<UserControl.Resources>
<DataTemplate x:Key="MyTemplate">
<Grid>
<Grid.Resources>
<Converters:ExpandedToLineRowsConverter ExpandedLines="5"
CollapsedLines="1"
x:Key="ExpandedToLines"/>
</Grid.Resources>
<TextBox MaxLines="{Binding IsExpanded,
Converter={StaticResource ExpandedToLines},
RelativeSource={RelativeSource FindAncestor,
AncestorLevel=1,
AncestorType={x:Type Expander}},
UpdateSourceTrigger=PropertyChanged}"
MinLines="{Binding IsExpanded,
Converter={StaticResource ExpandedToLines},
RelativeSource={RelativeSource FindAncestor,
AncestorLevel=1,
AncestorType={x:Type Expander}},
UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<Expander Header="{Binding}"
HeaderTemplate="{StaticResource MyTemplate}">
<!-- other wpf controls under expander, they do not affect the problem -->
</Expander>
</UserControl>
ExpandedToLineRowsConverter is very simple:
public class ExpandedToLineRowsConverter : IValueConverter
{
public int ExpandedLines { get; set; }
public int CollapsedLines { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? ExpandedLines : CollapsedLines;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return (int)value != CollapsedLines;
}
}
Problem is, it is working only when expanding, not when collapsing (textbox stay in multiline mode even when Expander.IsExpanded = false).
When i set breakpoint in converter, it is returning corect number of lines, but it looks like the TextBox just ignore them.
I have no idea what to do...
Edit: sample VS2012 project with problem
I have a requirement where on one DataGrid column, I need to show x number of clickable buttons side by side (horizontally stacked). The actual number or buttons to show depends on the binded value in that column.
The image below shows this. The left hand grid is my current one and and the right hand grid is what I am looking for.
Grid is binded to ViewModel like:
<DataGrid ItemsSource="{Binding employees}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=id}" Header="Id" />
<DataGridTextColumn Binding="{Binding Path=numberofplatforms}" Header="No" Width="50" IsReadOnly="True" />
</DataGrid.Columns>
</DataGrid>
And ViewModel is:
Public Class ViewModel
Public Property employees As ObservableCollection(Of employee)
Get
Return _employees
End Get
Set(ByVal value As ObservableCollection(Of employee))
_employees = value
End Set
End Property
Private _employees As New ObservableCollection(Of employee)
End Class
And the employee is:
Public Class employee
Public Property id As String
Public Property numberofplatforms As Integer
Public Property selectedplatform As Integer
End Class
On top of just displaying the buttons, the buttons themselve must act like radiobuttons, i.e. on any DataGrid row, only one button is "pushed down" (blue background buttons in the image) and the others are not pushed (gray background). Buttons (or shapes) must be clickable so that the selection can be changed.
Which button is "pushed" down is determined from the ViewModel, according to selectedplatform property. That property in this example is 1 for the ABC, 1 for the DEF and 2 for the GHI. If the numberofplatforms property is zero, no buttons are displayed (JKL).
How can I set up this mechanism?
Some parts of the code are in C#, I hope it's not a problem. Wpf code:
<Window.Resources>
<ResourceDictionary>
<local:PositiveIntegersConverter x:Key="PositiveIntegersConverter" />
<local:EmployeePlatformOptionConverter x:Key="EmployeePlatformOptionConverter"/>
</ResourceDictionary>
</Window.Resources>
<DataGrid ItemsSource="{Binding employees}" AutoGenerateColumns="False" x:Name="DataGrid1">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=id}" Header="Id" />
<DataGridTemplateColumn Header="No" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding numberofplatforms, Converter={StaticResource PositiveIntegersConverter}}" SelectedItem="{Binding selectedplatform}"
x:Name="ListBox1">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" Command="{Binding Source={x:Reference DataGrid1}, Path=DataContext.SelectOptionCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource EmployeePlatformOptionConverter}">
<Binding ElementName="ListBox1" Path="DataContext"/>
<Binding Path="."/>
</MultiBinding>
</Button.CommandParameter>
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Gray" />
<Setter Property="Foreground" Value="White" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}, Path=IsSelected}"
Value="True">
<Setter Property="Background" Value="Blue" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I used two converters here:
PositiveIntegersConverter which returns positive integers for a given numberofplatforms - these are available platform options for an employee
EmployeePlatformOptionConverter which converts two parameters that we want to pass to SelectOptionCommand: employee and selected platform option into one object of EmployeePlatformOption type.
public class PositiveIntegersConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var toInteger = System.Convert.ToInt32(value);
return toInteger > 0 ? Enumerable.Range(1, toInteger) : null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class EmployeePlatformOptionConverter
: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return new EmployeePlatformOption
{
Employee = values[0] as Employee,
Platform = (int)values[1]
};
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Using encapsulating object:
public class EmployeePlatformOption
{
public Employee Employee { get; set; }
public int Platform { get; set; }
}
you pass selection to SelectOptionCommand in your ViewModel class:
public ICommand SelectOptionCommand { get; set; }
private void SelectOptionExecute(EmployeePlatformOption employeePlatformOption)
{
if (employeePlatformOption != null && employeePlatformOption.Employee != null &&
employeePlatformOption.Platform > 0)
{
employeePlatformOption.Employee.selectedplatform = employeePlatformOption.Platform;
}
}
Employee should implement INotifyPropertyChange interface so selectedplatform update is visible on the screen.
I need to do something similar to a PriorityBinding, except the lower priority binding is used when the binding is null (not when the binding is invalid like in a PriorityBinding). I can't seem to figure out a 'good' way to do this short of creating two duplicate controls one with each binding I want to use and triggering their visibility based on whether or not the binding is null. There must be a better way as I do not want to update two controls every time I need to change something (duplicate code == bad).
Example:
when SelectedItem.SelectedItem is not null:
<ContentControl Content="{Binding SelectedItem.SelectedItem}"/>
and when SelectedItem.SelectedItem is null:
<ContentControl Content="{Binding SelectedItem}"/>
using a Style like this did not work:
<ContentControl Content="{Binding SelectedItem.SelectedItem}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem.SelectedItem}" Value="{x:Null}">
<Setter Property="Content" Value="{Binding SelectedItem}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
I'm guessing this did not work because the bindings in the style are trying to use the ContentControl's Content property as their source so the DataTrigger is acually testing SelectedItem.SelectedItem.SelectedItem.SelectedItem. Any ideas?
You could use MultiBinding to achive what you want:
<ContentControl Content="{Binding SelectedItem.SelectedItem}">
<ContentControl.Content>
<MultiBinding Converter="{StaticResource ResourceKey=myConverter}">
<Binding Path="SelectedItem"/>
<Binding Path="SelectedItem.SelectedItem"/>
</MultiBinding>
</ContentControl.Content>
</ContentControl>
Your Converter could look like
public class MyMultiConverter:IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[1] == null)
return values[0];
return values[1];
}
public object[] ConvertBack(object values, Type[] targetType, object parameter, CultureInfo culture)
{
return null;
}
}
I have a ListView with rows on it. When I click on a particular cell, I want that selected cell to be editable with a combo box and the thing is I have done it, but the combox box still remains even after editing. I want the combo box to change back to textblock.
<Style TargetType="{x:Type FrameworkElement}"
x:Key="GridEditStyle">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Visibility"
Value="{Binding Path=IsSelected,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListViewItem}},
Converter={StaticResource boolToVis},
ConverterParameter=True}" />
</Style>
<ComboBox SelectedItem="Present"
ItemsSource="{Binding ElementName=This,
Path=AvailablePublishers}"
Style="{StaticResource GridEditStyle}" />
code behind is
private ObservableCollection<string> _AvailablePublishers =
new ObservableCollection<string>();
public Student_Attendance()
{
InitializeComponent();
_AvailablePublishers.Add("Present");
_AvailablePublishers.Add("Absent");
_AvailablePublishers.Add("Late");
}
public ObservableCollection<string> AvailablePublishers
{ get { return _AvailablePublishers; } }
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
bool param = bool.Parse(parameter as string);
bool val = (bool)value;
return val == param ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
plz help me out
thank you
You know if you used a DataGrid, you would't have to do anything for switching the viewing and editing templates, it's done automatically ... here's a sample to get you started:
<DataGrid ItemsSource="{Binding ...}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding ...}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding ElementName=This, Path=AvailablePublishers}" SelectedItem="{Binding ...}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
I have an ItemsControl which lists items by separating them with a comma. The code is the following:
<ItemsControl ItemsSource="{Binding MyItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text=", "
Name="commaTextBlock"/>
<TextBlock Text="{Binding}"/>
</StackPanel>
<!-- Hide the first comma -->
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource PreviousData}}"
Value="{x:Null}">
<Setter Property="Visibility"
TargetName="commaTextBlock"
Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The result is something like this: Item1, Item2, Item3
Now, I'd like to do the same using a WrapPanel instead of a StackPanel as the ItemsPanelTemplate. I tested it and it works fine, except for a small detail, it does something like this:
Item1, Item2
, Item3
Of course this is because the comma is before each element and I hide the first one. I would like to put the comma after each element and hide the last one, so the result would be this:
Item1, Item2,
Item3
It would be really simple if there existed something like NextData (so I would bind to this instead of to PreviousData), but unfortunately no such thing exists (or I haven't found one). Does anyone have an idea how to solve this problem?
Thanks
I have done a visibility converter for a similar problem:
<TextBlock Text=", " Name="commaTextBlock">
<TextBlock.Visibility>
<MultiBinding Converter="{StaticResource commaVisibilityConverter}">
<Binding ElementName="myItemsControl" Path="ItemsSource"/>
<Binding/>
</MultiBinding>
</TextBlock.Visibility>
</TextBlock>
And the converter logic:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var collection = values[0] as IEnumerable<MyItem>;
var item = values[1] as MyItem;
if ((collection != null) && (item != null) && (collection.Count() > 0))
{
return collection.Last() == item ? Visibility.Hidden : Visibility.Visible;
}
return Visibility.Hidden;
}
Maybe you can try to use multibinding and a converter. Something like this:
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource myConverter}">
<Binding Mode="OneWay"/>
<Binding ElementName="root" Path="ItemsSource" Mode="OneWay"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
Where root is the name of your ItemsControl.
And write a converter which checks for position:
public class MyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var text = values[0] as string;
var list = values[1] as ObservableCollection<string>;
//check for null etc...
if (list.IndexOf(text) == list.Count - 1)
return text;
return string.Format("{0}, ", text);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
//ignore this, not gonna happen with OneWay Binding
return null;
}
}
Works for me! Hope it helps you with your problem!
EDIT:
Almost the same as the other answer, the difference here is that you only need 1 TextBlock in your template, and the converter decides if there is a comma or not. But basically the same principle. MultiBinding rocks! :-)