WPF use same datatemplate with different binding - wpf

I have the following datagrid
<Window
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" mc:Ignorable="d" x:Class="VenusProductInfoQueryWPF.MainWindow"
Height="350" Width="646" WindowStyle="None" ResizeMode="NoResize" Topmost="False" MouseLeftButtonDown="Window_MouseLeftButtonDown" AllowsTransparency="True" WindowStartupLocation="CenterScreen" ShowInTaskbar="True">
<Window.Resources>
<DataTemplate x:Key="DataTemplate1">
<Button Tag="{Binding Name}" Content="Show" Click="LinkButton_Click"></Button>
</DataTemplate>
<DataTemplate x:Key="DataTemplate2">
<Button Tag="{Binding Sex}" Content="Show" Click="LinkButton_Click"></Button>
</DataTemplate>
</Window.Resources>`
<Grid>
<DataGrid x:Name="MyDataGrid" HorizontalAlignment="Left" Margin="60,44,0,0"
VerticalAlignment="Top" Height="223" Width="402" AutoGenerateColumns="False"
AutoGeneratedColumns="MyDataGrid_AutoGeneratedColumns">
<DataGrid.Columns>
<DataGridTextColumn Header="Age" Binding="{Binding Path=Age}"></DataGridTextColumn>
<DataGridTemplateColumn Header="Sex" CellTemplate="{StaticResource DataTemplate2}"/>
<DataGridTemplateColumn Header="Name" CellTemplate="{StaticResource DataTemplate1}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
and in the codebehind I have:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Media;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
dataSource = new DataTable();
dataSource.Columns.Add("Age");
dataSource.Columns.Add("Name");
dataSource.Columns.Add("Sex");
AddNewRow(new object[] { 10, "wang", "Male" });
AddNewRow(new object[] { 15, "huang", "Male" });
AddNewRow(new object[] { 20, "gao", "Female" });
dataGrid1.ItemsSource = dataSource.AsDataView();
}
}
}
The datatable has more than 30 columns (I only wrote 2 in here to make it easier to follow).. the question is: If I want to show the same template style with different binging source in every column, do I really have to define many different datatemplates (like DataTemplate1, DataTemplate2, ... see above) to bind the CellTemplate of each DataGridTemplateColumn to it ? Can I define one datatemplate and in the code or through other way to dynamic set the binding? Thank you for your answer!

There is a way but it is not pretty,
For brevity I am using the code behind as the view model and I have removed exception handling and unnecessary lines.
I have convert your object array into a Person object (for reasons that should become evident later in the solution)
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Sex { get; set; }
}
I have converted your DataSource into an ObservableCollection So the code behind is now
public partial class MainWindow : Window
{
public MainWindow()
{
Items = new ObservableCollection<Person>()
{
new Person {Age = 10, Name = "wang", Sex="Male"},
new Person {Age = 15, Name = "huang", Sex="Male"},
new Person {Age = 20, Name = "gao", Sex="Female"}
};
ShowCommand = new DelegateCommand(ExecuteShowCommand, CanExecuteShowCommand);
InitializeComponent();
}
private bool CanExecuteShowCommand(object arg) { return true; }
private void ExecuteShowCommand(object obj) { MessageBox.Show(obj != null ? obj.ToString() : "No Parameter received"); }
public DelegateCommand ShowCommand { get; set; }
public ObservableCollection<Person> Items { get; set; }
}
The DelegateCommand is defined as
public class DelegateCommand : ICommand
{
private Func<object, bool> _canExecute;
private Action<object> _execute;
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
{
_canExecute = canExecute;
_execute = execute;
}
public bool CanExecute(object parameter) { return _canExecute.Invoke(parameter); }
void ICommand.Execute(object parameter) { _execute.Invoke(parameter); }
public event EventHandler CanExecuteChanged;
}
This allows the use of Commands and CommandParameters in the single template.
We then use a MultiValueConverter to get the data for each button. It uses the header of the column to interrogate, using reflection, the Person object for the required value and then pass it back as the parameter of the command.
public class GridCellToValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
object returnValue = null;
var person = values.First() as Person;
var propertyName = values[1] == DependencyProperty.UnsetValue ? string.Empty : (string)values[1];
if ((person != null) && (!string.IsNullOrWhiteSpace(propertyName))) { returnValue = person.GetType().GetProperty(propertyName).GetValue(person); }
return returnValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); }
}
The final piece of the puzzle is the xaml
<Window x:Class="StackOverflow.Q26731995.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:q26731995="clr-namespace:StackOverflow.Q26731995" Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<q26731995:GridCellToValueConverter x:Key="GridCell2Value" />
<DataTemplate x:Key="ButtonColumnDataTemplate">
<Button Content="Show" Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=ShowCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource GridCell2Value}">
<Binding /> <!-- The person object -->
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridCell}}" Path="Column.Header" /> <!-- The name of the field -->
</MultiBinding>
</Button.CommandParameter>
</Button>
</DataTemplate>
</Window.Resources>
<Grid>
<DataGrid x:Name="MyDataGrid" HorizontalAlignment="Left" Margin="60,44,0,0" ItemsSource="{Binding Path=Items}" VerticalAlignment="Top" Height="223" Width="402" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Age" Binding="{Binding Path=Age}"></DataGridTextColumn>
<DataGridTemplateColumn Header="Sex" CellTemplate="{StaticResource ButtonColumnDataTemplate}"/>
<DataGridTemplateColumn Header="Name" CellTemplate="{StaticResource ButtonColumnDataTemplate}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
You should be able to cut and past this code into a new wpf app and see it work. Not I have not take care of the AddNewItem row the grid adds (because we have not turned it off).
This can be done with a collection of object array rather than a collection of Person. To do so, you would need to pass the DataGridCellsPanel into the converter and use it with the header to calculate the index of the required value. The convert would look like
<MultiBinding Converter="{StaticResource GridCell2Value}">
<Binding /> <!-- The data -->
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridCell}}" Path="Column.Header}" /> <!-- The name of the field -->
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridCellsPanel}}" /> <!-- The panel that contains the row -->
</MultiBinding>
The converter code would be along the lines
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
object returnValue = null;
var data = values.First() as object[];
var property = values[1] == DependencyProperty.UnsetValue ? string.Empty : (string)values[1];
var panel = values[2] as DataGridCellsPanel;
var column = panel.Children.OfType<DataGridCell>().FirstOrDefault(c => c.Column.Header.Equals(property));
if (column != null)
{
returnValue = data[panel.Children.IndexOf(column)];
}
return returnValue;
}
I hope this helps.

Related

Set ContentPresenter ContentTemplate on value change in generated field

I have am attempting to build a tree view where:
1. The TreeViewItems are generated by a list in my model.
2. Each TreeViewItem contains a ComboBox, and a dynamic element whose template I want to change based on the value selected in the ComboBox.
Here is my current xaml code.
<Window x:Class="MyTestWPF.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:MyTestWPF"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:NodeTypeToTemplateConverter x:Key="NodeTypeToTemplateConverter"/>
<DataTemplate x:Key="Template1">
<TextBlock Text="Template 1" />
</DataTemplate>
<DataTemplate x:Key="Template2">
<TextBlock Text="Template 2" />
</DataTemplate>
<Style x:Key="MyNodeTemplate" TargetType="ContentPresenter">
<Setter Property="ContentTemplate" Value="{StaticResource Template1}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=NodeType}">
<DataTrigger.Value>
<local:NodeTypesEnum>Type1</local:NodeTypesEnum>
</DataTrigger.Value>
<Setter Property="ContentTemplate" Value="{Binding Converter={StaticResource NodeTypeToTemplateConverter}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
<HierarchicalDataTemplate DataType="{x:Type local:MyTreeNode}"
ItemsSource="{Binding Nodes}">
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding Path=GetAvailableNodeType}"
SelectedItem="{Binding Path=NodeType}" />
<ContentPresenter Style="{StaticResource MyNodeTemplate}" Content="{Binding}" />
</StackPanel>
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView x:Name="MyTree" ItemsSource="{Binding MyTreeModel}" />
</Window>
And its code-behind:
using System.Windows;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new
{
MyTreeModel = new MyTreeNode[] {
new MyTreeNode() { Name = "1", Nodes = new MyTreeNode[] { new MyTreeNode() { Name= "2" } } }
}
};
}
}
The tree node type:
namespace MyTestWPF
{
public class MyTreeNode
{
public string Name { get; set; }
public NodeTypesEnum NodeType { get; set; }
public MyTreeNode[] Nodes { get; set; }
public NodeTypesEnum[] GetAvailableNodeType()
{
return new NodeTypesEnum[] { NodeTypesEnum.Type1, NodeTypesEnum.Type2 };
}
}
public enum NodeTypesEnum
{
Type1 = 0,
Type2 = 1
}
}
The Converter (NodeTypeToTemplateConverter) receives the whole ViewModel, and returns the name of the relevant template based on values in the model.
using System;
using System.Globalization;
using System.Windows.Data;
namespace MyTestWPF
{
public class NodeTypeToTemplateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if((value as MyTreeNode).NodeType == NodeTypesEnum.Type1)
{
return "Template1";
} else
{
return "Template2";
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
The problem is that the above code causes a stack overflow exception. The first item in the TreeView endlessly calls NodeTypeToTemplateConverter's Convert method.
I figured it had to do with the DataTrigger.Value. Setting that to a value different from the default NodeType allows the page to load without overflow, but the moment any ComboBox is set to NodeType1, stack overflow.
I attempted to simply remove the DataTrigger.Value element, but that causes the Converter to never be called at all...
How can I dynamically build the template name based on the value selected by its neighboring ComboBox?
You probably want to use a DataTemplateSelector rather than a converter.
public class ComboBoxItemTemplateSelector : DataTemplateSelector
{
public DataTemplate Template1 { get; set; }
public DataTemplate Template2 { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
//Logic to select template based on 'item' value.
if (item == <template1Value>) return Template1; //TODO: replace <template1Value>
else if (item == <template2Value>) return Template2; //TODO: replace <template2Value>
else return new DataTemplate();
}
}
<local:ComboBoxItemTemplateSelector x:Key="ComboBoxItemTemplateSelector">
<local:ComboBoxItemTemplateSelector.Template1>
<DataTemplate>
<TextBlock Text="" />
</DataTemplate>
</local:ComboBoxItemTemplateSelector.Template1>
<local:ComboBoxItemTemplateSelector.Template2>
<DataTemplate>
<TextBlock Text="" />
</DataTemplate>
</local:ComboBoxItemTemplateSelector.Template2>
</local:ComboBoxItemTemplateSelector>
<ContentPresenter Content="{Binding NodeType}" ContentTemplateSelector="{StaticResource ComboBoxItemTemplateSelector}"/>
I have not fully tested this code, so let me know if you have any issues.
EDIT:
The template selector is only executed when the content changes so this won't work if you use {Binding}. A workaround for this would be to have the DataTemplate content bind to the parent's DataContext.
<DataTemplate>
<TextBlock Text="" DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ContentPresenter}}"/>
</DataTemplate>
If this workaround is not acceptable, there are other ways to do this as well.

wpf Contentcontrol validation

how to make Validation work on ContentControl ?
There is a class that is responsible for storing a value with units of measure, it is displayed in the content control through a dataset, I would like to check for correctness when displaying and editing it, for example, units cannot have a value of 2, or the value should be less than 10,000. solve this with multibinding, the validation rule is not satisfied when editing values.
Xaml file:
<Window x:Class="DELETE1.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:DELETE1"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
mc:Ignorable="d"
x:Name="m_win"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type local:ValWithUnits}">
<Border>
<DockPanel>
<ComboBox DockPanel.Dock="Right" Width="60" IsEnabled="True" SelectedIndex="{Binding Unit}" ItemsSource="{Binding Src, ElementName=m_win}" />
<TextBox Text="{Binding Val, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEnabled="True"/>
</DockPanel>
</Border>
</DataTemplate>
<local:MultiBindingConverter x:Key="MultiBindingConverter" />
</Window.Resources>
<Grid>
<ContentControl x:Name="m_contentControl">
<ContentControl.Content>
<MultiBinding Mode="TwoWay" NotifyOnValidationError="True"
UpdateSourceTrigger="PropertyChanged"
diag:PresentationTraceSources.TraceLevel="High"
Converter="{StaticResource MultiBindingConverter}" >
<Binding Path="Val" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" diag:PresentationTraceSources.TraceLevel="High"></Binding>
<Binding Path="Unit" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" diag:PresentationTraceSources.TraceLevel="High"></Binding>
<MultiBinding.ValidationRules>
<local:ContentControlValidationRule ValidatesOnTargetUpdated="True" x:Name="MValidationRule"/>
</MultiBinding.ValidationRules>
</MultiBinding>
</ContentControl.Content>
</ContentControl>
</Grid>
</Window>
And code file:
public class ValWithUnits:INotifyPropertyChanged
{
private double m_val;
private int m_unit;
public double Val
{
get => m_val;
set
{
m_val = value;
OnPropertyChanged(nameof(Val));
}
}
public int Unit
{
get => m_unit;
set
{
m_unit = value;
OnPropertyChanged(nameof(Unit));
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ValWithUnits MyValueWithUnits { get; set; } = new ValWithUnits();
public MainWindow()
{
InitializeComponent();
var a = new Binding("MyValueWithUnits");
a.Source = this;
a.ValidatesOnDataErrors = true;
a.ValidatesOnExceptions = true;
a.NotifyOnValidationError = true;
m_contentControl.SetBinding(ContentControl.ContentProperty, a);
}
public IEnumerable<int> Src => new int[] { 1,2,3};
}
public class ContentControlValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
//Debug.Fail("validate");
return ValidationResult.ValidResult;
}
private static ValidationResult BadValidation(string msg) =>
new ValidationResult(false, msg);
}
public class MultiBindingConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return string.Format("{0}-{1}", values[0], values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new object[] { 1, 1 };
}
}
Validation 'ContentControlValidationRule' which I install through multi-binding does not work.

DataGridTextColumn showing/editing time only, date bound to property of parent usercontrol

I have a DataGrid within a UserControl. I want to have a DataGridTextColumn bound to a DateTime field, showing only the time. When the user enters a time, the date portion (year, month, day) should be taken from a property (AttendDate) on the UserControl.
My first thought was to bind the user control's property to ConverterParameter:
<DataGridTextColumn Header="From"
Binding="{Binding FromDate, Converter={StaticResource TimeConverter},ConverterParameter={Binding AttendDate,ElementName=UC}}"
/>
but ConverterParameter doesn't take a binding. I then thought to do this using a MultiBinding:
<DataGridTextColumn Header="משעה" Binding="{Binding FromDate, Converter={StaticResource TimeConverter}}" />
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource TimeConverter}">
<Binding Path="FromDate" />
<Binding Path="AttendDate" ElementName="UC" />
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
However IMultiValueConverter.Convert -- which takes multiple parameters -- is only called when formatting the display. IMultiValueConverter.ConvertBack which is called on editing, only takes one parameter - the entered string.
How can I do this?
(I am not using MVVM; not something I can change.)
One idea for the solution is to have another property with only a getter that merges the info you want.
something like
property string Time {get {return this.FromDate.toshortdate().tostring() + AttendDate.hour.tostring() + attenddate.minutes.tostring()}; }
The code might be not exactly this, but then you can bind this property to show the info you want where it should be presented.
regards,
=============EDIT===========
I tried this, a very simple example...don't know if it works for you:
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv ="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<conv:TimeConverter x:Key="tmeConverter"></conv:TimeConverter>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding listExample}">
<DataGrid.Columns>
<DataGridTextColumn Header="teste" >
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource tmeConverter}">
<Binding Path="FromDate" />
<Binding Path="AttendDate" />
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Code behind (.cs)
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public List<Example2> listExample { get; set; }
public Example2 Test { get; set; }
public MainWindow()
{
InitializeComponent();
this.listExample = new List<Example2>();
//listExample.Add(new Example { IsChecked = false, Test1 = "teste" });
//listExample.Add(new Example { IsChecked = false, Test1 = "TTTTT!" });
this.Test = new Example2 { AttendDate = "1ui", FromDate = "ff" };
this.listExample.Add(this.Test);
DataContext = this;
}
}
}
And Example2 class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApplication1
{
public class Example2
{
public string FromDate { get; set; }
public string AttendDate { get; set; }
}
}
Converter:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace WpfApplication1
{
class TimeConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return values[0].ToString() + values[1].ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
The final window appears 3 columns, and if I change the last two columns, the first one is automatically edited.
Is that it?
Get rid of DataGridTextColumn and use DataGridTemplateColumn with CellTemplate containing a textblock bound to your multibinding, and cell editing template containing TextBox bound to FromDate, possibly via short-date converter, depending on usability you intend to achieve.
On of possible solutions:
XAML
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label>From time</Label>
<DatePicker SelectedDate="{Binding FromTime}"/>
</StackPanel>
<DataGrid ItemsSource="{Binding AllUsers}" AutoGenerateColumns="False">
<DataGrid.Resources>
<local:TimeConverter x:Key="TimeConverter"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="local:UserViewModel">
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TimeConverter}">
<Binding ElementName="root" Path="DataContext.FromTime"/>
<Binding Path="AttendTimeHour"/>
<Binding Path="AttendTimeMinute"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="local:UserViewModel">
<StackPanel Orientation="Horizontal">
<TextBlock>
<Run>Attend on </Run>
<Run Text="{Binding ElementName=root, Path=DataContext.FromTime, StringFormat=d}"/>
<Run> at </Run>
</TextBlock>
<TextBox Text="{Binding AttendTimeHour}"/><TextBlock>:</TextBlock>
<TextBox Text="{Binding AttendTimeMinute}"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
View model (UserViewModel) part:
private DateTime _attendTime;
public DateTime AttendTime
{
get { return _attendTime; }
set
{
if (value == _attendTime) return;
_attendTime = value;
OnPropertyChanged();
OnPropertyChanged("AttendTimeHour");
OnPropertyChanged("AttendTimeMinute");
}
}
public int AttendTimeHour
{
get { return attendTimeHour; }
set
{
if (value.Equals(attendTimeHour)) return;
attendTimeHour = value;
AttendTime = new DateTime(AttendTime.Year, AttendTime.Month, AttendTime.Day, AttendTimeHour, AttendTime.Minute, AttendTime.Second);
OnPropertyChanged();
}
}
private int _attendTimeMinute;
public int AttendTimeMinute
{
get { return _attendTimeMinute; }
set
{
if (value == _attendTimeMinute) return;
_attendTimeMinute = value;
AttendTime = new DateTime(AttendTime.Year, AttendTime.Month, AttendTime.Day, AttendTime.Hour, AttendTimeMinute, AttendTime.Second);
OnPropertyChanged();
}
}
And the converter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length < 2) return null;
if (!(values[0] is DateTime && values[1] is int && values[2] is int)) return null;
var fromDate = (DateTime) values[0];
var attendTimeHour = (int) values[1];
var attendTimeMinutes = (int)values[2];
var result = new DateTime(fromDate.Year, fromDate.Month, fromDate.Day, attendTimeHour, attendTimeMinutes, 0);
return result.ToString();
}

WPF Combobox, bind a collection<items> where item has a bool property isSelected?

I have a List< Versions> where Version, amongst others has the properties VersionUUID, Label, SKU and IsSelected. I would like to bind this to a Combobox and have the selected item just select the IsSelected flag (unselected any previous set flag).
Note:The combobox is in a template, used inside a datagrid cell, so I can not just bind a SelectedItem to the model!
What I have so far is working, the datagrid updates the DB as expected, however the initial value is not set onLoad. If one version already has a IsSelected=true, I would like to have that showing int the Combobox, but it is always empty unless i Select one from the list.
<DataTemplate x:Key="dtDatagridVersionSelector">
<ComboBox Margin="0" Width="90" Style="{StaticResource DatagridComboBox}"
ItemsSource="{Binding Path=Versions, Mode=OneTime}">
<ComboBox.ItemTemplate >
<DataTemplate >
<RadioButton Focusable="false" IsEnabled="true"
GroupName="{Binding VersionUUID}"
IsChecked="{Binding IsSelected, Mode=TwoWay}">
<StackPanel Orientation="Horizontal" >
<TextBlock Margin="3,0,0,0" Text="{Binding Label}"/>
<TextBlock Foreground="Red" Margin="3,0,0,0"
Text="{Binding SKU}"/>
</StackPanel>
</RadioButton>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=OneWay}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</DataTemplate>
Also, the use of the Radiobox is not written in stone, if there is a better solution to achieve this so only one item isSelected, I'm all open for it
Thanx for any pointers
Andreas
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:SelectedItemConverter x:Key="selectedItemConverter"/>
</Window.Resources>
<Grid>
<ComboBox ItemsSource="{Binding Students}" SelectedItem="{Binding Students, Converter={StaticResource selectedItemConverter}}" DisplayMemberPath="Name"/>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Students = new ObservableCollection<Student>();
Students.Add(new Student() { Name = "HArish", RollNo = 1, IsSelected = false });
Students.Add(new Student() { Name = "Arev", RollNo = 2, IsSelected = false });
Students.Add(new Student() { Name = "Pankaj", RollNo = 3, IsSelected = true });
Students.Add(new Student() { Name = "Deepak", RollNo = 4, IsSelected = false });
DataContext = this;
}
public ObservableCollection<Student> Students { get; set; }
}
public class Student
{
public string Name { get; set; }
public int RollNo { get; set; }
public bool IsSelected { get; set; }
}
public class SelectedItemConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && value is IEnumerable<Student>)
return ((IEnumerable<Student>)value).Where(s => s.IsSelected).FirstOrDefault();
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
I hope this will help.

Bind ItemsControl with ItemsSource to the array index

I want to bind an ItemsControl to an array of object, using ItemsSource and DataTemplate.
And I want to show the index of each item.
Like
Customer 1:
Name: xxxx
Age:888
Customer 2:
Name: yyy
Age: 7777
The simplest way to do this is to add an Index property to you class ;-) Otherwise it can be done using a MultiValueConverter:
<Window x:Class="IndexSpike.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
xmlns:Converters="clr-namespace:IndexSpike"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
Name="Root"
>
<Window.Resources>
<Converters:GetIndex x:Key="GetIndexConverter"/>
</Window.Resources>
<StackPanel>
<ItemsControl ItemsSource="{Binding Persons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Margin="0,5,0,0">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource
GetIndexConverter}"
StringFormat="Index: {0}">
<Binding Path="."/>
<Binding ElementName="Root" Path="Persons"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Text="{Binding Name, StringFormat='Name: {0}'}"/>
<TextBlock Text="{Binding Age, StringFormat='Age {0}'}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Window>
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace IndexSpike
{
public partial class Window1 : Window
{
public ObservableCollection<Person> Persons { get; set; }
public Window1()
{
Persons=new ObservableCollection<Person>
{
new Person("Me",20),
new Person("You",30)
};
InitializeComponent();
DataContext = this;
}
}
public class Person
{
public Person(string name,int age)
{
Name = name;
Age=age;
}
public string Name { get; set; }
public int Age { get; set; }
}
public class GetIndex:IMultiValueConverter
{
#region Implementation of IMultiValueConverter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var persons = (ObservableCollection<Person>) values[1];
var person = (Person) values[0];
return persons.IndexOf(person);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
}
Thanks to this question I found an other way to declare the MultiBinding:
<MultiBinding Converter="{StaticResource GetIndexConverter}"
StringFormat="Index: {0}">
<Binding Path="."/>
<Binding RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type ItemsControl}}"
Path="DataContext.Persons"/>
</MultiBinding>

Resources