Combining text with data - c#

I have a string like follows.
string someInfo = string.Format("First Name = {0}, Last Name = {1}",firstName, lastName);
This string need to be displayed in application using TextBlock. The first and last names are coming from database so I would like to using data bindings for this. Is it possible to do?

Yes, its possible.
However, because you have multiple bindings, you need to bind to a MultiBinding (MSDN).
Your binding looks like:
<TextBlock.Text>
<MultiBinding Converter="{StaticResource NameConverter}">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
With a MultiValueConverter:
public class NameConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return string.Format("First Name = {0}, Last Name = {1}", values[0], values[1]);
}
public objct ConvertBack(...)
{
return Binding.DoNothing;
}
}

I don't know whether you use the MVVM pattern. if you do just define a property in your ViewModel
public string Someinfo
{
get { return string.Format("First Name = {0}, Last Name = {1}",firstName, lastName);}
}
and then use a Binding in your Xaml
<TextBlock Text={Binding Path Someinfo} />
I would say this is 'cleaner' than doing that in your xaml.

yes it possible
public string SomeInfo { get; set; }
public MainWindow()
{
InitializeComponent();
SomeInfo = GetFirstNameAndLastNameFromDataBase();
DataContext = this;
}
private string GetFirstNameAndLastNameFromDataBase()
{
string firstName = "firstName";
string lastName = "lastName";
return string.Format("First Name = {0}, Last Name = {1}", firstName, lastName);
}
<Window x:Class="BindingToTextBlock.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock Text="{Binding SomeInfo, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBlock>
</Grid>
</Window>

Related

Bind custom header controls in DataGrid

I have a custom column header where each column's header has TextBox which contains name of the column and ComboBox, which contains information about the type of the column, e.g. "Date", "Number", etc.
I'm trying to bind ComboBox and keep its value somewhere, so that when user selects new value from ComboBox I can recreate table with the column's type changed. Basically all I need is to store somehow each ComboBox's value in a list somehow. I want to do the same with TextBox which should contain name of the column.
This is what I have so far.
<DataGrid x:Name="SampleGrid" Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="2" ItemsSource="{Binding SampledData}">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding ., Mode=OneWay}"/>
<ComboBox>
// How can I set it from ViewModel?
<ComboBoxItem Content="Date"></ComboBoxItem>
<ComboBoxItem Content="Number"></ComboBoxItem>
</ComboBox>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
</DataGrid>
ViewModel:
private DataTable _sampledData = new DataTable();
public DataTable SampledData
{
get => _sampledData;
set { _sampledData = value; NotifyOfPropertyChange(() => SampledData); }
}
Solutions in code behind are welcome too as long as I can pass the mappings to ViewModel later.
EDIT:
I've been trying to make this work with a List of ViewModels, but no luck:
public class ShellViewModel : Screen
{
public List<MyRowViewModel> Rows { get; set; }
public ShellViewModel()
{
Rows = new List<MyRowViewModel>
{
new MyRowViewModel { Column1 = "Test 1", Column2= 1 },
new MyRowViewModel { Column1 = "Test 2", Column2= 2 }
};
}
}
View
<DataGrid ItemsSource="{Binding Rows}">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding ., Mode=OneWay}"/>
<ComboBox ItemsSource="{Binding ??????}" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
</DataGrid>
Row
public class MyRowViewModel : PropertyChangedBase
{
public string Column1 { get; set; }
public int Column2 { get; set; }
}
EDIT2:
To clarify, I need a solution that will handle dynamic number of columns, so some files may store 3 columns and some might store 40 columns. I use this for parsing csv files to later display the data. In order to do that I have to know what types of values the file contains. Because some types may be ambiguous, I let the user decide which types they want. This is identical to Excel's "Load From File" wizard.
The wizard loads a small chunk of data (100 records) and allows user to decide what type the columns are. It automatically parses the columns to:
Let user see how the data will look like
Validate if the column can actually be parsed (e.g. 68.35 cannot
be parsed as DateTime)
Another thing is naming each column. Someone might load csv with each column named C1, C2... but they want to assign meaningful names such as Temperature, Average. This of course has to be parsed later too, because two columns cannot have the same name, but I can take care of this once I have a bindable DataGrid.
Let's break your problem into parts and solve each part separately.
First, the DataGrid itemsource, to make things easier, let's say that our DataGrid has only two columns, column 1 and column 2. A basic model for the DataGrid Items should looks like this:
public class DataGridModel
{
public string FirstProperty { get; set; }
public string SecondProperty { get; set; }
}
Now, assuming that you have a MainWindow (with a ViewModel or the DataContext set to code behind) with a DataGrid in it , let's define DataGridCollection as its ItemSource:
private ObservableCollection<DataGridModel> _dataGridCollection=new ObservableCollection<DataGridModel>()
{
new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"},
new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"},
new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"}
};
public ObservableCollection<DataGridModel> DataGridCollection
{
get { return _dataGridCollection; }
set
{
if (Equals(value, _dataGridCollection)) return;
_dataGridCollection = value;
OnPropertyChanged();
}
}
Second, now the interesting part, the columns structure. Let's define a model for your DataGrid's columns, the model will hold all the required properties to set your DataGrid columns, including:
-DataTypesCollection: a collection that holds the combobox itemsource.
-HeaderPropertyCollection: a collection of Tuples, each Tuple represent a column name and a data type, the data type is basically the selected item of column's combobox.
public class DataGridColumnsModel:INotifyPropertyChanged
{
private ObservableCollection<string> _dataTypesCollection = new ObservableCollection<string>()
{
"Date","String","Number"
};
public ObservableCollection<string> DataTypesCollection
{
get { return _dataTypesCollection; }
set
{
if (Equals(value, _dataTypesCollection)) return;
_dataTypesCollection = value;
OnPropertyChanged();
}
}
private ObservableCollection<Tuple<string, string>> _headerPropertiesCollection=new ObservableCollection<Tuple<string, string>>()
{
new Tuple<string, string>("Column 1", "Date"),
new Tuple<string, string>("Column 2", "String")
}; //The Dictionary has a PropertyName (Item1), and a PropertyDataType (Item2)
public ObservableCollection<Tuple<string,string>> HeaderPropertyCollection
{
get { return _headerPropertiesCollection; }
set
{
if (Equals(value, _headerPropertiesCollection)) return;
_headerPropertiesCollection = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now in you MainWindow's viewmodel (or codebehind) define an instance of the DataGridColumnsModel that we will be using to hold our DataGrid structure:
private DataGridColumnsModel _dataGridColumnsModel=new DataGridColumnsModel();
public DataGridColumnsModel DataGridColumnsModel
{
get { return _dataGridColumnsModel; }
set
{
if (Equals(value, _dataGridColumnsModel)) return;
_dataGridColumnsModel = value;
OnPropertyChanged();
}
}
Third, getting the column's TextBox's value. For that w'll be using a MultiBinding and a MultiValueConverter, the first property that w'll be passing to the MultiBinding is the collection of tuples that we define (columns' names and datatypes): HeaderPropertyCollection, the second one is the current column index that w'll retrieve from DisplayIndex using an ancestor binding to the DataGridColumnHeader:
<TextBox >
<TextBox.Text>
<MultiBinding Converter="{StaticResource GetPropertConverter}">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type Window}}" Path="DataGridColumnsModel.HeaderPropertyCollection"/>
<Binding Path="DisplayIndex" Mode="OneWay" RelativeSource="{RelativeSource RelativeSource={x:Type DataGridColumnHeader}}"/>
</MultiBinding>
</TextBox.Text>
The converter will simply retrieve the right item using the index from collection of tuples:
public class GetPropertConverter:IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
try
{
var theCollection = values[0] as ObservableCollection<Tuple<string, string>>;
return (theCollection?[(int)values[1]])?.Item1; //Item1 is the column name, Item2 is the column's ocmbobox's selectedItem
}
catch (Exception)
{
//use a better implementation!
return null;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Fourth, The last part is to update the DataGrid's ItemSource when the Combobox's selection changed, for that you could use the Interaction tools defined in System.Windows.Interactivity namespace (which is part of Expression.Blend.Sdk, use NuGet to install it: Install-Package Expression.Blend.Sdk):
<ComboBox ItemsSource="{Binding DataGridColumnsModel.DataTypesCollection,RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding UpdateItemSourceCommand,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
Each time the selectionChanged event occurred, update your DataGrid's ItemSource in the UpdateItemSourceCommand that should be added to your mainWindow's ViewModel:
private RelayCommand _updateItemSourceCommand;
public RelayCommand UpdateItemSourceCommand
{
get
{
return _updateItemSourceCommand
?? (_updateItemSourceCommand = new RelayCommand(
() =>
{
//Update your DataGridCollection, you could also pass a parameter and use it.
//Update your DataGridCollection based on DataGridColumnsModel.HeaderPropertyCollection
}));
}
}
Ps: the RelayCommand class i am using is part of GalaSoft.MvvmLight.Command namespace, you could add it via NuGet, or define your own command.
Finally here the full xaml code:
Window x:Class="WpfApp1.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:WpfApp1"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<local:GetPropertConverter x:Key="GetPropertConverter"/>
</Window.Resources>
<Grid>
<DataGrid x:Name="SampleGrid" ItemsSource="{Binding DataGridCollection}" AutoGenerateColumns="False">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBox >
<TextBox.Text>
<MultiBinding Converter="{StaticResource GetPropertConverter}">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type Window}}" Path="DataGridColumnsModel.HeaderPropertyCollection"/>
<Binding Path="DisplayIndex" Mode="OneWay" RelativeSource="{RelativeSource AncestorType={x:Type DataGridColumnHeader}}"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
<ComboBox ItemsSource="{Binding DataGridColumnsModel.DataTypesCollection,RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding UpdateItemSourceCommand,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="First Column" Binding="{Binding FirstProperty}" />
<DataGridTextColumn Header="Second Column" Binding="{Binding SecondProperty}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
And view models / codebehind:
public class GetPropertConverter:IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
try
{
var theCollection = values[0] as ObservableCollection<Tuple<string, string>>;
return (theCollection?[(int)values[1]])?.Item1; //Item1 is the column name, Item2 is the column's ocmbobox's selectedItem
}
catch (Exception)
{
//use a better implementation!
return null;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class DataGridColumnsModel:INotifyPropertyChanged
{
private ObservableCollection<string> _dataTypesCollection = new ObservableCollection<string>()
{
"Date","String","Number"
};
public ObservableCollection<string> DataTypesCollection
{
get { return _dataTypesCollection; }
set
{
if (Equals(value, _dataTypesCollection)) return;
_dataTypesCollection = value;
OnPropertyChanged();
}
}
private ObservableCollection<Tuple<string, string>> _headerPropertiesCollection=new ObservableCollection<Tuple<string, string>>()
{
new Tuple<string, string>("Column 1", "Date"),
new Tuple<string, string>("Column 2", "String")
}; //The Dictionary has a PropertyName (Item1), and a PropertyDataType (Item2)
public ObservableCollection<Tuple<string,string>> HeaderPropertyCollection
{
get { return _headerPropertiesCollection; }
set
{
if (Equals(value, _headerPropertiesCollection)) return;
_headerPropertiesCollection = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class DataGridModel
{
public string FirstProperty { get; set; }
public string SecondProperty { get; set; }
}
public partial class MainWindow : Window,INotifyPropertyChanged
{
private RelayCommand _updateItemSourceCommand;
public RelayCommand UpdateItemSourceCommand
{
get
{
return _updateItemSourceCommand
?? (_updateItemSourceCommand = new RelayCommand(
() =>
{
//Update your DataGridCollection, you could also pass a parameter and use it.
MessageBox.Show("Update has ocured");
}));
}
}
private ObservableCollection<DataGridModel> _dataGridCollection=new ObservableCollection<DataGridModel>()
{
new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"},
new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"},
new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"}
};
public ObservableCollection<DataGridModel> DataGridCollection
{
get { return _dataGridCollection; }
set
{
if (Equals(value, _dataGridCollection)) return;
_dataGridCollection = value;
OnPropertyChanged();
}
}
private DataGridColumnsModel _dataGridColumnsModel=new DataGridColumnsModel();
public DataGridColumnsModel DataGridColumnsModel
{
get { return _dataGridColumnsModel; }
set
{
if (Equals(value, _dataGridColumnsModel)) return;
_dataGridColumnsModel = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Result:
Update
You will achieve the same result by setting AutoGenerateColumns="True" and creating you columns dynamically.
This is not exactly a complete answer but more a hint towards what I think your looking to do, if so you can query me for additional information.
I think what you want to do is define let say a DataGridColumDef type such as this:
public class DataGridColumnDef : NotifyPropertyChangeModel
{
public string Name
{
get => _Name;
set => SetValue(ref _Name, value);
}
private string _Name;
public Type DataType
{
get => _DataType;
set => SetValue(ref _DataType, value);
}
private Type _DataType;
public DataGridColumnDef(string name, Type type)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
DataType = type ?? throw new ArgumentNullException(nameof(type));
}
}
Then I imagine your view model acting as the data context for the DataGrid could look something like this:
public class MainViewModel : NotifyPropertyChangeModel
{
public ObservableList<DataGridColumnDef> ColumnDefinitions
{
get => _ColumnDefinitions;
set => SetValue(ref _ColumnDefinitions, value);
}
private ObservableList<DataGridColumnDef> _ColumnDefinitions;
public ObservableList<DataGridRowDef> RowDefinitions
{
get => _RowDefinitions;
set => SetValue(ref _RowDefinitions, value);
}
private ObservableList<DataGridRowDef> _RowDefinitions;
public MainViewModel()
{
// Define initial columns
ColumnDefinitions = new ObservableList<DataGridColumnDef>()
{
new DataGridColumnDef("Column 1", typeof(string)),
new DataGridColumnDef("Column 2", typeof(int)),
};
// Create row models from initial column definitions
RowDefinitions = new ObservableList<DataGridRowDef>();
for(int i = 0; i < 100; ++i)
{
RowDefinitions.Add(new DataGridRowDef(ColumnDefinitions));
// OR
//RowDefinitions.Add(new DataGridRowDef(ColumnDefinitions, new object[] { "default", 10 }));
}
}
}
This way on the main view model you could subscribe to collection/property changed events in the ColumnDefinitions property and then re-create the rows collection.
Now the trick that I am not 100% sure would work, but not sure why it wouldn't, is making your DataGridRowDef type inherit from DynamicObject so you can spoof members and their values, something like this:
public class DataGridRowDef : DynamicObject
{
private readonly object[] _columnData;
private readonly IList<DataGridColumnDef> _columns;
public static object GetDefault(Type type)
{
if (type.IsValueType)
{
return Activator.CreateInstance(type);
}
return null;
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return _columns.Select(c => c.Name).Union(base.GetDynamicMemberNames());
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var columnNames = _columns.Select(c => c.Name).ToList();
if(columnNames.Contains(binder.Name))
{
var columnIndex = columnNames.IndexOf(binder.Name);
result = _columnData[columnIndex];
return true;
}
return base.TryGetMember(binder, out result);
}
public DataGridRowDef(IEnumerable<DataGridColumnDef> columns, object[] columnData = null)
{
_columns = columns.ToList() ?? throw new ArgumentNullException(nameof(columns));
if (columnData == null)
{
_columnData = new object[_columns.Count()];
for (int i = 0; i < _columns.Count(); ++i)
{
_columnData[i] = GetDefault(_columns[i].DataType);
}
}
else
{
_columnData = columnData;
}
}
}
Anyway if this kind of solution seems approachable to you I can try and work through it a bit more possibly.
Try this.
Window1.xaml
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<this:RowDataConverter x:Key="RowDataConverter1" />
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding Rows, Mode=OneWay}">
<DataGrid.Columns>
<DataGridTextColumn>
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource RowDataConverter1}">
<Binding Path="Column1" Mode="OneWay" />
<Binding Path="Column1OptionString" Mode="OneWay" RelativeSource="{RelativeSource AncestorType=Window, Mode=FindAncestor}" />
</MultiBinding>
</DataGridTextColumn.Binding>
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="Column Header 1" />
<ComboBox ItemsSource="{Binding ColumnOptions, Mode=OneWay, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}"
SelectedValue="{Binding Column1OptionString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}"
SelectedValuePath="Option">
<ComboBox.ItemTemplate>
<DataTemplate DataType="this:ColumnOption">
<TextBlock Text="{Binding Option, Mode=OneTime}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Window1.xaml.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication1
{
public partial class Window1 : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public List<RowData> Rows { get; set; }
public List<ColumnOption> ColumnOptions { get; set; }
private string _column1OptionString;
public string Column1OptionString
{
get
{
return _column1OptionString;
}
set
{
_column1OptionString = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Column1OptionString"));
}
}
public Window1()
{
InitializeComponent();
ColumnOptions = new List<ColumnOption>() {
new ColumnOption(){ Option = "String", StringFormat = "" },
new ColumnOption(){ Option = "Int32", StringFormat = "" }
};
Rows = new List<RowData>() {
new RowData(){ Column1 = "01234" }
};
_column1OptionString = "String";
this.DataContext = this;
}
}
public class ColumnOption
{
public string Option { get; set; }
public string StringFormat { get; set; }
}
public class RowData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private object _column1;
public object Column1
{
get
{
return _column1;
}
set
{
_column1 = value;
if (PropertyChanged!= null)
PropertyChanged(this, new PropertyChangedEventArgs("Column1"));
}
}
}
public class RowDataConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values[1] == null)
return values[0].ToString();
switch (values[1].ToString())
{
case "String":
return values[0].ToString();
case "Int32":
Int32 valueInt;
Int32.TryParse(values[0].ToString(), out valueInt);
return valueInt.ToString();
default:
return values[0].ToString();
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
UPDATE
based on #FCin comment
"This is nice, but I use this to load csv files and the number of columns changes depending of csv file. Here I have to hardcore each column, but some files may have 1 column and some might have 30 columns".
Assume your csv file using format:
line1: Headers,
line2: Data Type,
line3-end: Records.
Example data1.csv:
ColumnHeader1,ColumnHeader2
Int32,String
1,"A"
2,"B"
3,"C"
I try to parse csv file using TextFieldParser, then generate the DataGrid's columns programmatically.
Window2.xaml
<Window x:Class="WpfApplication1.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<Label Content="File:" />
<ComboBox x:Name="FileOption"
SelectionChanged="FileOption_SelectionChanged">
<ComboBox.Items>
<Run Text="Data1.csv" />
<Run Text="Data2.csv" />
</ComboBox.Items>
</ComboBox>
</StackPanel>
<DataGrid x:Name="DataGrid1" Grid.Row="1"
AutoGenerateColumns="False"
ItemsSource="{Binding ListOfRecords, Mode=OneWay}">
</DataGrid>
</Grid>
</Window>
Window2.xaml.cs
using Microsoft.VisualBasic.FileIO;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
namespace WpfApplication1
{
public partial class Window2 : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
List<myDynamicObject> _listOfRecords;
public List<myDynamicObject> ListOfRecords
{
get
{
return _listOfRecords;
}
}
public Window2()
{
InitializeComponent();
DataContext = this;
}
public void LoadData(string fileName)
{
_listOfRecords = new List<myDynamicObject>();
myDynamicObject record;
TextFieldParser textFieldParser = new TextFieldParser(fileName);
textFieldParser.TextFieldType = FieldType.Delimited;
textFieldParser.SetDelimiters(",");
string[] headers = null;
string[] dataTypes = null;
string[] fields;
int i = 0;
while(!textFieldParser.EndOfData)
{
fields = textFieldParser.ReadFields();
if (i == 0)
{
headers = fields;
}
else if (i == 1)
{
dataTypes = fields;
}
else
{
record = new myDynamicObject();
for (int j = 0; j < fields.Length; j++)
{
switch(dataTypes[j].ToLower())
{
case "string":
record.SetMember(headers[j], fields[j]);
break;
case "int32":
Int32 data;
if (Int32.TryParse(fields[j], out data))
{
record.SetMember(headers[j], data);
}
break;
default:
record.SetMember(headers[j], fields[j]);
break;
}
}
_listOfRecords.Add(record);
}
i += 1;
}
PropertyChanged(this, new PropertyChangedEventArgs("ListOfRecords"));
DataGrid1.Columns.Clear();
for (int j = 0; j < headers.Length; j++)
{
DataGrid1.Columns.Add(new DataGridTextColumn()
{
Header = headers[j],
Binding = new Binding()
{
Path = new PropertyPath(headers[j]),
Mode = BindingMode.OneWay
}
});
}
}
private void FileOption_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
LoadData((FileOption.SelectedItem as Run).Text);
}
}
public class myDynamicObject : DynamicObject
{
Dictionary<string, object> dictionary = new Dictionary<string, object>();
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string name = binder.Name;
return dictionary.TryGetValue(name, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
dictionary[binder.Name] = value;
return true;
}
public void SetMember(string propertyName, object value)
{
dictionary[propertyName] = value;
}
}
}

IValueConverter - Source and Converter in the same Binding/ Same Control using both the Source and Converter

I have a set of comboboxes and textboxes like this:
C1 T1
C2 T2
C3 T3
I implemented an IValueConverter to set the TimeZone in C1 and get the corresponding time in T1. Same for the other pairs.
What I want to do is : if a user manually changes the time in T1, the time in T2 and T3 must change corresponding to T1 as well as according to the TimeZone.
T1 is not the reference though. If any of the textboxes have their value changed, all other textboxes must change as well.
This change can happen:
If the TimeZone is changed in the Combobox
if the User manually changes the time by typing in the text box
Here is my full code:
public partial class MainWindow : Window
{
public static int num;
public static bool isUserInteraction;
public static DateTime timeAll;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
ReadOnlyCollection<TimeZoneInfo> TimeZones = TimeZoneInfo.GetSystemTimeZones();
this.DataContext = TimeZones;
cmb_TZ1.SelectedIndex = 98;
cmb_TZ2.SelectedIndex = 46;
cmb_TZ3.SelectedIndex = 84;
cmb_TZ4.SelectedIndex = 105;
cmb_TZ5.SelectedIndex = 12;
}
private void ComboBox_Selection(object Sender, SelectionChangedEventArgs e)
{
var cmbBox = Sender as ComboBox;
DateTime currTime = DateTime.UtcNow;
TimeZoneInfo tst = (TimeZoneInfo)cmbBox.SelectedItem;
if (isUserInteraction)
{
/* txt_Ctry1.Text=
txt_Ctry2.Text =
txt_Ctry3.Text =
txt_Ctry4.Text =
txt_Ctry5.Text =*/
isUserInteraction = false;
}
}
private void TextBox_Type(object Sender, TextChangedEventArgs e)
{
var txtBox = Sender as TextBox;
if (isUserInteraction)
{
timeAll = DateTime.Parse(txtBox.Text);
if (txtBox.Name != "txt_Ctry1")
txt_Ctry1.Text=
if (txtBox.Name != "txt_Ctry2")
txt_Ctry2.Text =
if (txtBox.Name != "txt_Ctry3")
txt_Ctry3.Text =
if (txtBox.Name != "txt_Ctry4")
txt_Ctry4.Text =
if (txtBox.Name != "txt_Ctry5")
txt_Ctry5.Text =
isUserInteraction = false;
}
}
private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
isUserInteraction = true;
}
}
public class TimeZoneConverter : IValueConverter
{
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
if (MainWindow.isUserInteraction == false)
{
return value == null ? string.Empty : TimeZoneInfo
.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Utc, (TimeZoneInfo)value)
.ToString("HH:mm:ss dd MMM yy");
}
else
{
return value == null ? string.Empty : TimeZoneInfo
.ConvertTime(MainWindow.timeAll, TimeZoneInfo.Utc, (TimeZoneInfo)value)
.ToString("HH:mm:ss dd MMM yy");
}
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
}
XAML:
<Window x:Class="Basic_WorldClock.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:src="clr-namespace:System;assembly=mscorlib"
xmlns:sys="clr-namespace:System;assembly=System.Core"
xmlns:local="clr-namespace:Basic_WorldClock"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Window.Resources>
<ObjectDataProvider x:Key="timezone" ObjectType="{x:Type
sys:TimeZoneInfo}" MethodName="GetSystemTimeZones">
</ObjectDataProvider>
<local:TimeZoneConverter x:Key="timezoneconverter"/>
</Window.Resources>
<Grid Margin="0,0.909,0,-0.909">
<TextBox x:Name="txt_Time1" Text="{Binding ElementName=cmb_TZ1, Path=SelectedValue, Converter={StaticResource timezoneconverter}}" VerticalAlignment="Top"/>
<TextBox x:Name="txt_Time2" Text="{Binding ElementName=cmb_TZ2, Path=SelectedValue, Converter={StaticResource timezoneconverter}}" VerticalAlignment="Top"/>
<TextBox x:Name="txt_Time3" Text="{Binding ElementName=cmb_TZ3, Path=SelectedValue, Converter={StaticResource timezoneconverter}}" Height="23.637" VerticalAlignment="Bottom"/>
<ComboBox x:Name="cmb_TZ1" SelectionChanged="ComboBox_Selection" PreviewMouseDown="OnPreviewMouseDown" ItemsSource="{Binding Source={StaticResource timezone}}" HorizontalAlignment="Right" Height="22.667" Margin="0,89.091,51.667,0" VerticalAlignment="Top" Width="144.666"/>
<ComboBox x:Name="cmb_TZ2" SelectionChanged="ComboBox_Selection" PreviewMouseDown="OnPreviewMouseDown" ItemsSource="{Binding Source={StaticResource timezone}}" HorizontalAlignment="Right" Height="22.667" Margin="0,131.091,52.667,0" VerticalAlignment="Top" Width="144.666"/>
<ComboBox x:Name="cmb_TZ3" SelectionChanged="ComboBox_Selection" PreviewMouseDown="OnPreviewMouseDown" ItemsSource="{Binding Source={StaticResource timezone}}" HorizontalAlignment="Right" Height="22.667" Margin="0,0,48.334,123.575" VerticalAlignment="Bottom" Width="144.666"/>
</Grid>
Question: How can I cascade the corresponding changes to other text boxes by using the Convert method the same way the combox does?
I can use the TextChanged method to capture changes in the reference text box and I can add a few lines of code to make those changes, but I want to use the IValueConverter. Can I have the Source and Converter in the same binding for a textbox?
Without a good Minimal, Complete, and Verifiable code example that shows clearly what you've got so far, it's difficult to provide precise information. But based on what you've described, it seems the main problem here is that you're not using the normal "view model"-based techniques that WPF is designed to be used with. In addition, you are binding the Text property to the time zone, rather than the time, so what WPF wants to update when the Text property changes is actually the combo box selection rather than the time itself.
The first thing you need to do is, instead of having your controls refer to each other, create a view model class that represents the actual state you want to display, and then use that for your DataContext in the window, binding appropriate properties to the particular controls.
And what are those appropriate properties? Well, based on your description you actually have just four: 1) The actual time, and 2) through 4) the three time zones you want to handle.
So, something like this:
class ViewModel : INotifyPropertyChanged
{
// The actual time. Similar to the "timeAll" field you have in the code now
// Should be kept in UTC
private DateTime _time;
// The three selected TimeZoneInfo values for the combo boxes
private TimeZoneInfo _timeZone1;
private TimeZoneInfo _timeZone2;
private TimeZoneInfo _timeZone3;
public DateTime Time
{
get { return _time; }
set { UpdateValue(ref _time, value); }
}
public TimeZoneInfo TimeZone1
{
get { return _timeZone1; }
set { UpdateValue(ref _timeZone1, value); }
}
public TimeZoneInfo TimeZone2
{
get { return _timeZone2; }
set { UpdateValue(ref _timeZone2, value); }
}
public TimeZoneInfo TimeZone3
{
get { return _timeZone3; }
set { UpdateValue(ref _timeZone3, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void UpdateValue<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (!object.Equals(field, value))
{
field = value;
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
(People often encapsulate the PropertyChanged event and UpdateValue() method in a base class that can be reused for all your view model types.)
With that, then you write an implementation of IMultiValueConverter that takes as the input the combo box index (i.e. Index1, Index2, or Index3 as appropriate) and the Time property value, uses those two values to produce the time-zone converted value for the text box, which is bound using those two values and the converter.
The converter's Convert() method will do the above conversion. Then you'll need to make the ConvertBack() method use the appropriate combo box value to convert back to the UTC time.
Unfortunately, there's a bit of a wrinkle here. Your converter will not normally have access to that value. The IMultiValueConverter.ConvertBack() method only gets the bound target value, and is expected to convert back to the bound source values from that. It's not designed to allow you to update one source value based on another source value and the target value.
There are a number of ways around this limitation, but none that I know of are very elegant.
One option uses the view model exactly as I've shown above. The trick is that you'll need to pass via ConverterParameter a reference to the ComboBox associated with the bound Text property, so that the ConvertBack() method can use the currently selected value (you can't pass the currently selected value itself as the ConverterParamater value, because ConverterParameter is not a dependency property and so can't be the target of a property binding).
Done this way, you might have a converter that looks like this:
class TimeConverter : IMultiValueConverter
{
public string Format { get; set; }
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
DateTime utc = (DateTime)values[0];
TimeZoneInfo tzi = (TimeZoneInfo)values[1];
return tzi != null ? TimeZoneInfo.ConvertTime(utc, tzi).ToString(Format) : Binding.DoNothing;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
string timeText = (string)value;
DateTime time;
if (!DateTime.TryParseExact(timeText, Format, null, DateTimeStyles.None, out time))
{
return new object[] { Binding.DoNothing, Binding.DoNothing };
}
ComboBox comboBox = (ComboBox)parameter;
TimeZoneInfo tzi = (TimeZoneInfo)comboBox.SelectedValue;
return new object[] { TimeZoneInfo.ConvertTime(time, tzi, TimeZoneInfo.Utc), Binding.DoNothing };
}
}
And XAML that looks like this:
<Window x:Class="TestSO38517212BindTimeZoneAndTime.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:l="clr-namespace:TestSO38517212BindTimeZoneAndTime"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:ViewModel/>
</Window.DataContext>
<Window.Resources>
<ObjectDataProvider x:Key="timezone"
ObjectType="{x:Type s:TimeZoneInfo}"
MethodName="GetSystemTimeZones">
</ObjectDataProvider>
<l:TimeConverter x:Key="timeConverter" Format="HH:mm:ss dd MMM yy"/>
<p:Style TargetType="ComboBox">
<Setter Property="Width" Value="200"/>
</p:Style>
<p:Style TargetType="TextBox">
<Setter Property="Width" Value="120"/>
</p:Style>
</Window.Resources>
<StackPanel>
<TextBlock Text="{Binding Time}"/>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="comboBox1" ItemsSource="{Binding Source={StaticResource timezone}}" SelectedValue="{Binding TimeZone1}"/>
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource timeConverter}"
ConverterParameter="{x:Reference Name=comboBox1}"
UpdateSourceTrigger="PropertyChanged">
<Binding Path="Time"/>
<Binding Path="SelectedValue" ElementName="comboBox1"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="comboBox2" ItemsSource="{Binding Source={StaticResource timezone}}" SelectedValue="{Binding TimeZone2}"/>
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource timeConverter}"
ConverterParameter="{x:Reference Name=comboBox2}"
UpdateSourceTrigger="PropertyChanged">
<Binding Path="Time"/>
<Binding Path="SelectedValue" ElementName="comboBox2"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="comboBox3" ItemsSource="{Binding Source={StaticResource timezone}}" SelectedValue="{Binding TimeZone3}"/>
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource timeConverter}"
ConverterParameter="{x:Reference Name=comboBox3}"
UpdateSourceTrigger="PropertyChanged">
<Binding Path="Time"/>
<Binding Path="SelectedValue" ElementName="comboBox3"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
</StackPanel>
</StackPanel>
</Window>
This will work fine at run-time. But you will get design-time _"Object reference not set to an instance of an object" error messages, due to the use of {x:Reference ...} in the ConverterParameter assignments. To some, this is a minor inconvenience, but I find it a huge annoyance and am willing to go to a fair amount of effort to avoid it. :)
So, here's a completely different approach, which foregoes the converter altogether and puts all of the logic inside the view model itself:
class ViewModel : INotifyPropertyChanged
{
private string _ktimeFormat = "HH:mm:ss dd MMM yy";
// The actual time. Similar to the "timeAll" field you have in the code now
// Should be kept in UTC
private DateTime _time = DateTime.UtcNow;
// The three selected TimeZoneInfo values for the combo boxes
private TimeZoneInfo _timeZone1 = TimeZoneInfo.Utc;
private TimeZoneInfo _timeZone2 = TimeZoneInfo.Utc;
private TimeZoneInfo _timeZone3 = TimeZoneInfo.Utc;
// The text to display for each local time
private string _localTime1;
private string _localTime2;
private string _localTime3;
public ViewModel()
{
_localTime1 = _localTime2 = _localTime3 = _time.ToString(_ktimeFormat);
}
public DateTime Time
{
get { return _time; }
set { UpdateValue(ref _time, value); }
}
public TimeZoneInfo TimeZone1
{
get { return _timeZone1; }
set { UpdateValue(ref _timeZone1, value); }
}
public TimeZoneInfo TimeZone2
{
get { return _timeZone2; }
set { UpdateValue(ref _timeZone2, value); }
}
public TimeZoneInfo TimeZone3
{
get { return _timeZone3; }
set { UpdateValue(ref _timeZone3, value); }
}
public string LocalTime1
{
get { return _localTime1; }
set { UpdateValue(ref _localTime1, value); }
}
public string LocalTime2
{
get { return _localTime2; }
set { UpdateValue(ref _localTime2, value); }
}
public string LocalTime3
{
get { return _localTime3; }
set { UpdateValue(ref _localTime3, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void UpdateValue<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (!object.Equals(field, value))
{
field = value;
OnPropertyChanged(propertyName);
}
}
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
switch (propertyName)
{
case "TimeZone1":
LocalTime1 = Convert(TimeZone1);
break;
case "TimeZone2":
LocalTime2 = Convert(TimeZone2);
break;
case "TimeZone3":
LocalTime3 = Convert(TimeZone3);
break;
case "LocalTime1":
TryUpdateTime(LocalTime1, TimeZone1);
break;
case "LocalTime2":
TryUpdateTime(LocalTime2, TimeZone2);
break;
case "LocalTime3":
TryUpdateTime(LocalTime3, TimeZone3);
break;
case "Time":
LocalTime1 = Convert(TimeZone1);
LocalTime2 = Convert(TimeZone2);
LocalTime3 = Convert(TimeZone3);
break;
}
}
private void TryUpdateTime(string timeText, TimeZoneInfo timeZone)
{
DateTime time;
if (DateTime.TryParseExact(timeText, _ktimeFormat, null, DateTimeStyles.None, out time))
{
Time = TimeZoneInfo.ConvertTime(time, timeZone, TimeZoneInfo.Utc);
}
}
private string Convert(TimeZoneInfo timeZone)
{
return TimeZoneInfo.ConvertTime(Time, timeZone).ToString(_ktimeFormat);
}
}
This version of the view model includes the formatted text values. Rather than using a converter to format, everything is done here in response to property change notifications that are being raised by the view model itself.
In this version, the view model does get a lot more complicated. But it's all very straightforward, easily understood code. And the XAML winds up a lot simpler:
<Window x:Class="TestSO38517212BindTimeZoneAndTime.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:l="clr-namespace:TestSO38517212BindTimeZoneAndTime"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:ViewModel/>
</Window.DataContext>
<Window.Resources>
<ObjectDataProvider x:Key="timezone"
ObjectType="{x:Type s:TimeZoneInfo}"
MethodName="GetSystemTimeZones">
</ObjectDataProvider>
<p:Style TargetType="ComboBox">
<Setter Property="Width" Value="200"/>
</p:Style>
<p:Style TargetType="TextBox">
<Setter Property="Width" Value="120"/>
</p:Style>
</Window.Resources>
<StackPanel>
<TextBlock Text="{Binding Time}"/>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="comboBox1" ItemsSource="{Binding Source={StaticResource timezone}}" SelectedValue="{Binding TimeZone1}"/>
<TextBox Text="{Binding LocalTime1, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="comboBox2" ItemsSource="{Binding Source={StaticResource timezone}}" SelectedValue="{Binding TimeZone2}"/>
<TextBox Text="{Binding LocalTime2, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="comboBox3" ItemsSource="{Binding Source={StaticResource timezone}}" SelectedValue="{Binding TimeZone3}"/>
<TextBox Text="{Binding LocalTime3, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</StackPanel>
</Window>
Either of these should directly address the issue you're asking about, i.e. allowing changes in one of multiple values that are derived from a single value being converted to be propagated back to the other values. But if neither of those suit you, you have a number of other options.
One of the most obvious is to simply subscribe to the appropriate property-changed events in each control and then explicitly copy back to the other controls the values you want. IMHO that would be very inelegant, but it wouldn't necessarily require the use of the view-model paradigm, and so it could be argued that would be more consistent with your original example.
Another approach would be to make your converter much more heavyweight, by making it inherit DependencyObject so that it can have a dependency property bound as the target of the time zone value. You would still need to also use the IMultiBindingConverter approach to set the target Text property, but this would allow for a less hacky way to make sure the time zone information is available in the ConvertBack().
You can see an example of this approach in this answer to Get the Source value in ConvertBack() method for IValueConverter implementation in WPF binding. Note that with this approach, each binding will require its own separate instance of the converter. No sharing as a resource.

ComboBox Validation without Binding

This question is based on this solution, but I somehow cannot get this to work using a ComboBox. I would like to validate that upon clicking a submit button, the combobox has a selected item and is not null. Please note that I am not binding to anything on purpose, and not because I don't know how to. But if the answer is that there is no way I can use the validation rules without binding (to ComboBoxes specifically, I've done it to textboxes via the linked solution), please let me know.
Here is what I have so far:
XAML:
<ComboBox DataContext="{StaticResource ChargeAssigneeViewSource}" Name="ChargeAssigneeBox" ItemsSource="{Binding}" Width="85">
<ComboBox.SelectedItem>
<Binding RelativeSource="{RelativeSource Self}" Path="GetType" Mode="TwoWay">
<Binding.ValidationRules>
<my:ComboBoxValidationRule ErrorMessage="Please select an Assignee" />
</Binding.ValidationRules>
</Binding>
</ComboBox.SelectedItem>
</ComboBox>
Validation Rule:
class ComboBoxValidationRule : ValidationRule
{
private string errorMessage;
public string ErrorMessage
{
get { return errorMessage; }
set { errorMessage = value; }
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (value == null)
return new ValidationResult(false, ErrorMessage);
return new ValidationResult(true, null);
}
}
Button Click:
private void AddAnHours()
{
Employee currEmployee;
if (!ValidateElement.HasError(ChargeAssigneeBox))
{
if (!ValidateElement.HasError(analystTimeTxtBox))
{
currEmployee = ChargeAssigneeBox.SelectedItem as Employee;
item.AddTime(currEmployee, DateTime.Now, double.Parse(analystTimeTxtBox.Text));
analystTimeTxtBox.Clear();
ChargeAssigneeBox.SelectedItem = null;
}
}
UpdateTotals();
}
The error that I get is in this line:
currEmployee = ChargeAssigneeBox.SelectedItem as Employee;
but my ItemsSource is binding properly so even though I have selected an item, the selecteditem is not converting it to an employee object. I suspect it has something to do with what I am binding it to:
<Binding RelativeSoruce="{RelativeSource Self}" Path="GetType"....>
Help would be greatly appreciated, thanks.
You can't bind a property to the selectedItem on the combobox and then check if thats property is null?
Public string SelectedComboboxItem { get; set; }
<ComboBox DataContext="{StaticResource ChargeAssigneeViewSource}" SelectedItem="{Binding SelectedComboboxItem}" Name="ChargeAssigneeBox" ItemsSource="{Binding}" Width="85">

Binding doesn't work in ListView

I have a wpf control named DataPicker which has a dependency property named SelectedDate.
In simple cases it works well but there is one case where binding fails and I can't understand why: when i try to bind it inside a ListView.
For example, I have class (INotifyPropertyChanged is implemented)
public class TestClass : INotifyPropertyChanged
{
public string Name { get; set; }
public DateTime Date { get; set; }
}
and try to bind sample collection like
public ObservableCollection<TestClass> Items { get; set; }
which has one element in it.
Binding looks like
<Window x:Class="Neverov.Test.Window1"
x:Name="this"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Neverov.Framework;assembly=Neverov.Framework">
<Grid>
<ListView ItemsSource="{Binding ElementName=this, Path=Items}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<local:DatePicker SelectedDate="{Binding Date, Mode=TwoWay}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
and Name property works fine.
inside my DatePicker date value is shown this way:
<TextBox x:Name="PART_TextBox">
<TextBox.Text>
<Binding Path="SelectedDate"
RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:DatePicker}}"
Mode="TwoWay"
Converter="{StaticResource DateTimeConverter}"
ConverterParameter="d">
</Binding>
</TextBox.Text>
</TextBox>
any ideas why this could happen?
More code of the DatePicker class: (some specific properties that I need I'll rather miss to keep code size not so large)
[TemplatePart(Name = PartPopup, Type = typeof(Popup))]
[TemplatePart(Name = PartMonthBack, Type = typeof(ButtonBase))]
[TemplatePart(Name = PartMonthForward, Type = typeof(ButtonBase))]
[TemplatePart(Name = PartDates, Type = typeof(Selector))]
[TemplatePart(Name = PartTextBox, Type = typeof(TextBox))]
[TemplatePart(Name = PartCheckBox, Type = typeof(CheckBox))]
[TemplatePart(Name = PartToggleButton, Type = typeof(ToggleButton))]
public class DatePicker : Control, INotifyPropertyChanged
{
...
public static readonly DependencyProperty SelectedDateProperty =
DependencyProperty.Register("SelectedDate",
typeof(DateTime?),
typeof(DatePicker),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(sender, e) =>
{
var datePicker = sender as DatePicker;
if (datePicker != null)
{
var oldValue = e.OldValue as DateTime?;
DateTime selectedDateForPopup =
datePicker.SelectedDate ??
DateTime.Now;
datePicker.CurrentlyViewedMonth =
selectedDateForPopup.Month;
datePicker.CurrentlyViewedYear =
selectedDateForPopup.Year;
datePicker.OnDateChanged(datePicker.SelectedDate, oldValue);
var popup = datePicker.GetTemplateChild(PartPopup) as Popup;
if (popup != null)
popup.IsOpen = false;
}
}));
... //a lot more not so important code here
}
Make sure your properties throw the INotifyPropertyChanged event:
public class TestClass : INotifyPropertyChanged
{
private DateTime date;
public DateTime Date
{
get { return date; }
set { date = value; NotifyPropertyChanged("Date"); }
}
private void NotifyPropertyChanged(string info)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
And make your binding to the date also TwoWay:
<local:DatePicker SelectedDate="{Binding Date, Mode=TwoWay}"/>
Check the output window for any data binding errors..
Probable errors:
DataContext not set correctly.
ListView ItemsSource="{Binding ElementName=this, Path=Items} Isn't Items a property of the Window or a ViewModel class ?
The problem is solved.
It was not in wrong bindings or other difficult and uneven stuff, but, as it was legacy code, somewhere in code programmer made a mistake.
Thank for all!

How to use MultiBinding in a WPF ComboBox

This is driving me NUTS!!!
I have a ComboBox used to filter a query by employee which works fine but only displays the employees first name. I want to use a MultiValueConverter to display the employees full name (This would be less urgent if we did not have 2 Mikes and 2 Daves)
Below is my working code and the IMultiValueConverter Class (With unnecessary formatting trimmed out for brevity). I have tried everything I can think of to get the MultiConverter to work but I have had no luck.
<ComboBox ItemsSource="{Binding Path=EmployeesFilter}"
DisplayMemberPath="EmpFirstName"
SelectedValue="{Binding Path=EmployeeToShow, Mode=TwoWay}"/>
The ViewModel Properties it is bound to:
// This collection is used to populate the Employee Filter ComboBox
private ObservableCollection<Employee> employeesFilter;
public ObservableCollection<Employee> EmployeesFilter
{
get {
return employeesFilter;
}
set {
if (employeesFilter != value)
{
employeesFilter = value;
OnPropertyChanged("EmployeesFilter");
}
}
}
// This property is TwoWay bound to the EmployeeFilters SelectedValue
private Employee employeeToShow;
public Employee EmployeeToShow
{
get {
return employeeToShow;
}
set {
if (employeeToShow != value)
{
employeeToShow = value;
OnPropertyChanged("EmployeeToShow");
QueryIssues(); // Requery with new employee filter
}
}
}
The IMultiValueConverter:
class StringsToFullNameMultiConverter : IMultiValueConverter
{
public object Convert(object[] values,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
// I added this because I kept getting DependecyProperty.UnsetValue
// Passed in as the program initializes
if (values[0] as string != null)
{
string firstName = (string)values[0];
string lastName = (string)values[1];
string fullName = firstName + " " + lastName;
return fullName;
}
return null;
}
public object[] ConvertBack(object value,
Type[] targetTypes,
object parameter,
System.Globalization.CultureInfo culture)
{
return null;
}
}
I tried a lot of different things but basically am using the following in the ComboBox
<ComboBox.SelectedValue>
<MultiBinding Converter="{StaticResource StringsToFullNameMultiConverter}"
Mode="OneWay" >
<Binding Path="EmpFirstName" />
<Binding Path="EmpLastName"/>
</MultiBinding>
</ComboBox.SelectedValue>
As it stands now the converter is called when the program initializes with the values set to DependencyProperty.UnsetValue. after that it is never called again, even when you select a name from the box. The names are still displayed as a first name.
Thanks for any help or pointers to good tutorials/samples you can provide. All the ones I keep finding on the web are for textboxes and I can use them just fine all day.
You're close! What you want to do though is ComboBox.ItemTemplate, not SelectedValue. Prepare for some XAML hell.
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource StringsToFillNameMultiConverter}">
<Binding Path="EmpFirstName" />
<Binding Path="EmpLastName" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
Also, if I recall correctly, you don't need to create your own converter if you're just formatting strings. I think you can do the following (someone please correct me if I'm wrong.)
<!-- "Last, First" -->
<MultiBinding StringFormat="{}{1}, {0}">
<Binding Path="EmpFirstName" />
<Binding Path="EmpLastName" />
</MultiBinding>
You may be better of using a data template for the items, so you have complete control over how each person is displayed in the dropdown list.
The type convert is OK provided you don’t have a need to control the formatting of the different fields.
I ended up by addig a Readonly Property to my Class and use Displaymemberpath in the Combobox
public class MyEmployee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string DisplayName {
get { return FirstName + " " + LastName; }
}
}
Could something like this work for your situation...?
BR,
Daniel

Categories

Resources