MultiBinding not working in DataGridTextColumn - c#

I am trying to use a MultiBinding to update a DataGridTextColumn.
<Window x:Class="WPFBench.MultiBindingProblem"
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:WPFBench"
mc:Ignorable="d"
Title="MultiBindingProblem" Height="300" Width="300">
<Window.Resources>
<local:MultiValueConverter x:Key="MultiValueConverter"/>
</Window.Resources>
<Grid>
<DataGrid x:Name="dataGrid" AutoGenerateColumns="False"
ItemsSource="{Binding TableA}" ColumnWidth="100*">
<DataGrid.Columns>
<DataGridTextColumn Header="Value" Binding="{Binding Value}"/>
<DataGridTextColumn Header="MultiValue">
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource MultiValueConverter}">
<Binding Path="Value"/>
<Binding Path="Value"/>
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
Here is the converter...
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace WPFBench
{
public class MultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
The code behind...
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace WPFBench
{
/// <summary>
/// Interaction logic for MultiBindingProblem.xaml
/// </summary>
public partial class MultiBindingProblem : Window
{
DataTable _tableA = new DataTable();
public MultiBindingProblem()
{
InitializeComponent();
_tableA.Columns.Add(new DataColumn("Value", typeof(double)));
_tableA.Rows.Add(0.0);
_tableA.Rows.Add(1.0);
_tableA.Rows.Add(2.0);
_tableA.Rows.Add(3.0);
_tableA.Rows.Add(4.0);
DataContext = this;
}
public DataTable TableA
{
get { return _tableA; }
}
}
}
The single binding column is updated. The multivalue converter is called. The multivalue column remains blank. What am I doing wrong?

Assuming that your Value is a value type like an int and not a string, this issue happens because there is no implicit conversion from object to string.
Try returning a
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return String.Format("{0}", values[0]); // don't return an object values[0];
}
from the Convert method.
Also put a breakpoint and check if values is correctly filled as expected.
Simple demo to prove that this is the correct answer.
Start you window in the question from a button of the main window
private void button_Click(object sender, RoutedEventArgs e)
{
MultiBindingProblem probl = new MultiBindingProblem();
probl.DataContext = new DemoRoughViewModel();
probl.Show();
}
using a simple, rough view model
public class DemoRoughTable
{
public int Value { get; set; } // Notice that it's not a string
}
public class DemoRoughViewModel
{
public List<DemoRoughTable> TableA { get; set; } = new List<DemoRoughTable>()
{
new DemoRoughTable() { Value = 1 }, new DemoRoughTable() { Value = 2 }
};
}
HTH

Your example should work, as long as you convert values[0] to its string representation in the Convert method of IMultiValueConverter (As explained by other answers here). However, your example here is a little strange, because there is no need for a MultiBinding. (I know you are aware of them, since the first column demonstrates a more proper approach).
Anyway, I think you need a MultiBinding for the Binding property of a DataGridTextColumn, when you want to set the Binding dynamically. In this case, you should send the DataContext and the path string, and retrieve its value in a IMultiValueConverter. There is an example here, similar to this situation, in which the Binding changes based on the value in the header of the DataGridTextColumn.
Hope it helps.

What am I doing wrong?
The Binding property of a DataGridTextColumn is supposed to be set to a BindingBase object and not to a data bound value.
If you intend to show a value returned by the converter in the column you should use a DataGridTemplateColumn:
<DataGridTemplateColumn Header="MultiValue">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MultiValueConverter}">
<Binding Path="Value"/>
<Binding Path="Value"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Also make sure that you are returning a string from the converter:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0].ToString();
}

Related

ComboBox object doesn't display initial value

A ComboBox object with a data binding, when I propram like below, the content doesn't display until a selection operation made.
XAML:
<Window x:Class="Recipe_GUI.ComboboxTest"
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:Recipe_GUI"
xmlns:src="clr-namespace:RecipeLib;assembly=RecipeLib"
mc:Ignorable="d"
Title="ComboboxTest" Height="450" Width="800">
<Window.Resources>
<src:DataType x:Key="DataType"/>
</Window.Resources>
<Grid>
<ComboBox HorizontalAlignment="Left" Margin="125,82,0,0" VerticalAlignment="Top" Width="120"
x:Name="C01"
ItemsSource="{StaticResource ResourceKey=DataType}"
SelectedValue="{Binding Path=DataType}"/>
<!--SelectedValue="{Binding Path=Text, ElementName=T01}"/>-->
<TextBox Text="{Binding ElementName=C01, Path=SelectedIndex}" Margin="125,141,309,249"/>
<TextBox Text="{Binding Path=DataType}" Margin="125,202,309,188" x:Name="T01"/>
</Grid>
</Window>
When I change the code to binding the ComboBox to TextBox "T01"("T01" binding to the same object), the ComboBox initial value displayed as expected. The code and presentation like below.
<ComboBox HorizontalAlignment="Left" Margin="125,82,0,0" VerticalAlignment="Top" Width="120"
x:Name="C01"
ItemsSource="{StaticResource ResourceKey=DataType}"
SelectedValue="{Binding Path=Text, ElementName=T01}"/>
<!--SelectedValue="{Binding Path=DataType}"/>-->
The other related code are like below.
XAML.CS:
using RecipeLib;
using System.Windows;
namespace Recipe_GUI
{
/// <summary>
/// Interaction logic for ComboboxTest.xaml
/// </summary>
public partial class ComboboxTest : Window
{
Param param = new Param();
public ComboboxTest()
{
InitializeComponent();
DataContext = param;
}
}
}
Class Param:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace RecipeLib
{
public class Param : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private Enum_DataType dataType;
public Enum_DataType DataType
{
get { return dataType; }
set
{
dataType = value;
OnPropertyChanged("DataType");
}
}
public Param()
{
DataType = Enum_DataType.Float;
}
}
}
Enum_DataType and DataType:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
namespace RecipeLib
{
public enum Enum_DataType : int
{
Float = 0,
Integer = 1,
Enumeration = 2,
Bitmask = 3,
Time = 4
}
public class DataType : ObservableCollection<string>
{
public DataType() : base()
{
foreach (var item in Enum.GetNames(typeof(Enum_DataType)))
{
Add(item);
}
}
}
}
Please help answer why the first method doesn't work for the initial value, and how to fix? Thank you in advance!
Old Code
<ComboBox HorizontalAlignment="Left" Margin="125,82,0,0"
VerticalAlignment="Top" Width="120"
x:Name="C01"
ItemsSource="{StaticResource ResourceKey=DataType}"
SelectedValue="{Binding Path=DataType}"/>
it's an assumption so if anyone can correct me then please do it.
I believe the ComboBox has a "Text" property and in this case, you are binding an enum so you need a converter to convert the value from enum to string. so I added a converter and it worked.
Additional: each ComboBoxItem has a "Content" property which is a string type so when you choose any of the items the ComboBox "Text" property is set automatically. that's why it works when you select any of the items.
the same logic applies to your code where you bound the text property of Textblock to ComboBox property "SelectedValue"
Updated Code
Here are some modifications I did to your existing code
Converter class
Option: I named it "EnumToStringConverter" but you can name this class whatever you want
public class EnumToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Enum.GetNames(value.GetType()).ElementAt((int)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Enum.Parse(targetType, value.ToString());
}
}
XAML
<Window.Resources>
<local:DataType x:Key="DataType"/>
<local:EnumToStringConverter x:Key="EnumConverter" />
</Window.Resources>
<ComboBox HorizontalAlignment="Left" Margin="125,82,0,0" VerticalAlignment="Top" Width="120"
x:Name="C01"
ItemsSource="{StaticResource DataType}"
SelectedValue="{Binding DataType, Converter={StaticResource EnumConverter}}" />

How to bind data from One control and Bind to source into a variable WPF, C#

I try to bind a selected value from DataGrid and show into the user on TextBlock.
Then bind it to a variable on the model in the source code
It is a little example code to make it more easy to show the problem here.
my xaml file:
<Window x:Class="WpfAppTest.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:WpfAppTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel>
<DataGrid x:Name="DGExample" MinWidth="50" SelectionMode="Single" FontSize="30"
ItemsSource="{Binding ExampleList, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
</DataGrid>
<TextBlock Name="TBDescription" MinWidth="100" FontSize="30">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} - {1}">
<Binding Path="Example" Mode="OneWayToSource" UpdateSourceTrigger="PropertyChanged" />
<Binding Path="SelectedItem.X" ElementName="DGExample" Mode="OneWay" UpdateSourceTrigger="PropertyChanged" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Name="ShowExample" FontSize="30" Text="{Binding Path=Example, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
</TextBlock>
</StackPanel>
</Grid>
</Window>
My model example :
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfAppTest
{
public class ModelExample : INotifyPropertyChanged
{
public int Example
{
get;
set;
}
public ObservableCollection<ClassExample> ExampleList { get; set; }
public ModelExample()
{
ExampleList = new ObservableCollection<ClassExample>() {
new ClassExample(1,2), new ClassExample(3,4), new ClassExample(5,6)};
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
My Example class :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfAppTest
{
public class ClassExample
{
public ClassExample(int x, int y)
{
X = x;
Y = y;
}
public int X{get;set;}
public int Y{ get; set; }
}
}
My fody file :
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<PropertyChanged />
</Weavers>
I expect to see the value in TBDescription Control to the Example Variable
But the only value stored in Example is 0 only...
The problem with your code is that it never binds value to Example property from TextBlock because its content isn't ever changed. So if you just want to bind selected value of DataGrid to Example do the following. Create converter which converts ClassExample instance to int
public class TestConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
ClassExample obj = value as ClassExample;
if (obj != null)
{
return obj.X;
}
return 0;
}
}
Add converter to Window
<Window.Resources>
<your-namespace:TestConverter x:Key="TestConverter" />
</Window.Resources>
Add binding to SelectedItem in DataGrid
<DataGrid
x:Name="DGExample"
MinWidth="50"
SelectionMode="Single"
FontSize="30"
ItemsSource="{Binding ExampleList, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Example, Mode=OneWayToSource, Converter={StaticResource TestConverter}}">

BoolToVisibilityConverter inside window.resources

I have a list box that contains a label and a text box that the user can alter. The list box contents are defined in a data template (inside window.resources). I would like to add a border to each item in the list that has been changed using a booltovisibility converter.
I think I'm having trouble because I'm trying to set the converter inside window.resources.
Can somebody please point me in the right direction?
View Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
namespace MaintainPersonData
{
public class MaintainPersonViewModel
{
public MaintainPersonViewModel(ObservableCollection<PersonViewModel> personList)
{
}
public INotifyUser Notifier;
private ObservableCollection<PersonViewModel> _personList;
public ObservableCollection<PersonViewModel> PersonList
{
get
{
return _personList;
}
set
{
_personList = value;
OnPropertyChanged("PersonList");
}
}
private bool _changesMade;
public bool ChangesMade
{
get
{
return _changesMade;
}
set
{
_changesMade = value;
OnPropertyChanged("ChangesMade");
}
}
private bool _hasErrors;
public bool HasErrors
{
get { return _hasErrors; }
set
{
_hasErrors = value;
if (!_hasErrors)
{
ErrorMessage = "";
}
OnPropertyChanged("HasErrors");
}
}
Xaml:
<Window.Resources>
<conv:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<DataTemplate x:Key="ListBoxItemTemplate">
<Border BorderBrush="LightGreen" BorderThickness="2" Visibility="{Binding ChangesMade, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid>
<TextBox x:Name="PersonTextBox" Text="{Binding PersonName, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox x:Name="PersonListBox" SelectionMode="Single" KeyboardNavigation.TabNavigation="Continue" ItemTemplate="{StaticResource ListBoxItemTemplate}" ItemsSource="{Binding PersonList}">
<!-- Code to highlight selected item (http://stackoverflow.com/questions/15366806/wpf-setting-isselected-for-listbox-when-textbox-has-focus-without-losing-selec) -->
</ListBox>
<!-- BoolToVisibilityConverter works perfectly here -->
<Label Name="ErrorLabel" Grid.Column="0" Foreground="Red" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="{Binding HasErrors, Converter={StaticResource BoolToVisibilityConverter}}" >
<TextBlock Text="{Binding ErrorMessage, UpdateSourceTrigger=PropertyChanged}" />
</Label>
</Grid>
</Window>
And Finally, the converter:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace MaintainRegexData
{
class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
ChangesMade should be defined per each PersonViewMode It's because right now you will (or will not) add border to all items. Have you checked that Convert method from BoolToVisibilityConverter is invoked? And last thing - there is nothing wrong with setting converter in window resources.
first of all you Bind your ItemsSource to PersonList which it's Type is ObservableCollection<PersonViewModel> where ChangesMade include in MaintainPersonViewModel so you need to place your ChangesMade inside PersonViewModel class and make changes while personName Property changed.
and don't forget what #Frank said about Border.

wpf datagrid. Highlight rows, which have omitted data between them

I have a datagrid filled with data, one of the columns stores item's id. If there are rows, which have omits in id, i need to highlight them, for example: row1 has id=40, row2 has id=42, id=41 is missind. Can i somehow implement this using datatriggers or something like that?
Rodrigo Silva's suggestion is good so do that if you can. If you can't/don't want to - here is one way that you can do it with a DataTrigger:
XAML:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:WpfApplication4"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid ItemsSource="{Binding list}">
<DataGrid.Resources>
<app:MissingRowCheckConverter x:Key="MissingRowCheckConverter" />
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource MissingRowCheckConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="Item" />
<Binding RelativeSource="{RelativeSource AncestorType=DataGrid}" Path="ItemsSource" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
</Grid>
</Window>
Code behind:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication4
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<MyData> list { get; set; }
public MainWindow()
{
InitializeComponent();
list = new ObservableCollection<MyData>();
for (int i = 1; i <= 100; i++)
{
if (i == 3)
continue;
MyData d = new MyData() { id = i, name = "Name " + i.ToString() };
list.Add(d);
}
DataContext = this;
}
}
public class MyData
{
public int id { get; set; }
public string name { get; set; }
}
public class MissingRowCheckConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values.Length > 1)
{
MyData d = values[0] as MyData;
if (d == null)
return false;
ObservableCollection<MyData> list = values[1] as ObservableCollection<MyData>;
if (list != null)
{
// Check if any in the list have id - 1
if (list.Any(aa => aa.id == (d.id - 1)))
return false;
// Check if this is the lowest id
if (!list.Any(aa => aa.id < d.id))
return false;
}
}
return true;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
}
Screenshot:

Unknown property 'Converter' for type 'MS.Internal.Markup.MarkupExtensionParser+UnknownMarkupExtension' encountered while parsing a Markup Extension

I am binding to an ObservableCollection called ScaleFactor to a ComboBox. The value of the ObservableCollection are simply 1, 2, 4 and 8. I want to use an IValueConverter to change these values to x1, x2, x4 and x8.
My MainWindow.xaml
<Window x:Class="TimeLineCanvas.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:timeline="clr-namespace:TimeLineCanvas.UserControls"
xmlns:helper="clr-namespace:TimeLineCanvas.Helpers"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<helper:ZoomConverter x:Key="ZoomConverter" />
</Grid.Resources>
<StackPanel>
<ComboBox ItemsSource="{Binding SSS}" HorizontalAlignment="Left" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding, Converter={StaticResource ZoomConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Grid>
</Window>
And the code behind
using System;
using System.Windows;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace TimeLineCanvas
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
#region Constructors
public MainWindow()
{
InitializeComponent();
SSS = new ObservableCollection<int>();
SSS.Add(1);
SSS.Add(2);
this.DataContext = this;
}
#endregion
public ObservableCollection<int> SSS { get; set; }
}
}
And the converter
using System;
using System.Windows.Data;
namespace TimeLineCanvas.Helpers
{
public class ZoomConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return "x" + value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
I don't know why this is, I'm not using MarkupExtensions so I don't think this link helps. Can any one shed any light?
Do not use a comma after Binding. This way you call the empty constructor on the Binding object.
{Binding, Converter={StaticResource ZoomConverter}}
should be
{Binding Converter={StaticResource ZoomConverter}}

Categories