Using Multi Binding in telerik Datagrid - wpf

How do I pass multiple parameters in command parameters.
here is what I am trying to do:
I want to send Is checked or not (I can do this by introducing a Boolean field to the object bound. But I dont want to do that) and I want to send the selected data object for that row.
<telerik:GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type telerik:GridViewDataControl}}, Path=DataContext.LineItemSelection}">
<CheckBox.CommandParameter>
<MultiBinding Converter="{StaticResource CommandConverter}">
<Binding Path="IsChecked" />
<Binding RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</CheckBox.CommandParameter>
</CheckBox>
</DataTemplate>
</telerik:GridViewColumn.CellTemplate>
UPDATE:
I added a class called selection Item. To see what the converter is getting.
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
SelectionItem si = new SelectionItem();
foreach (var value in values)
{
Type t = value.GetType();
if (t.FullName == "System.Boolean")
si.IsSelected = (bool) value;
else
{
si.SelectedCustomer = value as Customer;
}
}
return si;
}
The type of the second parameter is the checkbox itself if I use
<Binding RelativeSource="{RelativeSource Self}"/>
Here I want the data item that is bound to that row(in this case Customer).
I even tried using
<Binding RelativeSource= "{RelativeSource FindAncestor, AncestorType={x:Type telerik:GridViewColumn}}" Path="DataContext" />
But this is also coming in as null. why is this?

Try out simple binding by passing in converter current binding source, then cast to underlying object and based on IsChecked return a value.
<DataTemplate>
<CheckBox Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type telerik:GridViewDataControl}}, Path=DataContext.LineItemSelection}">
<CheckBox.CommandParameter>
<MultiBinding Converter="{StaticResource CommandConverter}">
<Binding Path="IsChecked" />
<Binding RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</CheckBox.CommandParameter>
</CheckBox>
</DataTemplate>
// CommandConverter should simply return the values
public object Convert(...)
{
return values;
}
Now in command handler you would be able access parameters:
public void OnLineItemSelection(object parameter)
{
object[] parameters = (object[])parameter;
bool isChecked = (double)parameters[0];
var instance = (TYPENAME)parameters[1];
}

using
<CheckBox Name="chkSelection" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type telerik:GridViewDataControl}}, Path=DataContext.LineItemSelection}">
<CheckBox.CommandParameter>
<MultiBinding Converter="{StaticResource CommandConverter}">
<Binding Path="IsChecked" ElementName="chkSelection" />
<Binding />
</MultiBinding>
</CheckBox.CommandParameter>
</CheckBox>
did the trick

Related

WPF) Why does the exact same binding work in one place but not another?

So i am at a complete loss why the exact same binding works for one element but not another (on the same control, code for binding is copy and pasted).
I have made a MultiValueConverter that takes in 4 values. values[0] determines which one of the values[1-3] should be returned. (Ternary logic)
This converter works great. I use this to choose which color and image a control should have based on an enum. But, when using the same converter for tooltip to choose between string, then i get a binding error.
The weird thing is that is that when i use the same converter inside a template for choosing which string for the ToolTip, then it works! The exact same code copy and pasted.
When i bind with the ToolTip (not in a template) the value[0] is "{DependencyProperty.UnsetValue}", instead of the enum that i have binded to.
Code inside a UserControl)
<v:ColoredImage Width="20" Height="20" HorizontalAlignment="Right">
<v:ColoredImage.Color> //THIS WORKS
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="ParamStatus" ElementName="pn"/> <-- SAME BINDING
<Binding Source="{StaticResource GreenLight}"/>
<Binding Source="{StaticResource YellowLight}"/>
<Binding Source="{StaticResource RedLight}"/>
</MultiBinding>
</v:ColoredImage.Color>
<v:ColoredImage.Image> // THIS WORKS
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="ParamStatus" ElementName="pn"/> <-- SAME BINDING
<Binding Source="{StaticResource OkIcon}"/>
<Binding Source="{StaticResource WarningIcon}"/>
<Binding Source="{StaticResource ErrorIcon}"/>
</MultiBinding>
</v:ColoredImage.Image>
<v:ColoredImage.ToolTip>
<ToolTip> //THIS PART DOES NOT WORK
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="ParamStatus" ElementName="pn"/> <-- SAME BINDING
<Binding Source="OK"/>
<Binding Source="Not Synced"/>
<Binding Source="ERROR"/>
</MultiBinding>
</ToolTip>
</v:ColoredImage.ToolTip>
</v:ColoredImage>
Code Inside a Style and ControlTemplate (this code work, even though it is the same)
<v:ColoredImage Height="24" Width="24" Margin="65,65,0,0" VerticalAlignment="Center">
<v:ColoredImage.Color>
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="Status" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Source="{StaticResource GreenLight}"/>
<Binding Source="{StaticResource YellowLight}"/>
<Binding Source="{StaticResource RedLight}"/>
</MultiBinding>
</v:ColoredImage.Color>
<v:ColoredImage.Image>
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="Status" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Source="{StaticResource OkIcon}"/>
<Binding Source="{StaticResource UnidentifiedIcon}"/>
<Binding Source="{StaticResource ErrorIcon}"/>
</MultiBinding>
</v:ColoredImage.Image>
<v:ColoredImage.ToolTip>
<ToolTip>
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="Status" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Source="OK"/>
<Binding Source="Unidentified"/>
<Binding Source="ERROR"/>
</MultiBinding>
</ToolTip>
</v:ColoredImage.ToolTip>
</v:ColoredImage>
I could fix this by doing a style/template for my first UserControl. But i feel like i shouldnt have too, and either way i wanna know why the EXACT same code works in one place but not another. I'm completely dumbfounded.
Code for the Converter, this is not where problem occurs, but i figured someone is going to ask me to post it anyway:
public class TernaryConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
int index = (int)values[0];
if (index < 0 || index > 2)
{
return values[1];
}
return values[index+1];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("EnumToImageConverter can only be used OneWay.");
}
}
The reason why ElementName="pn" doesn't work in a ToolTip is that a ToolTip resides in its own element tree and there is no element named "pn" in the namescope of this tree.

WPF MultiBinding Ellipse Fill

I am not able to get MultiBinding on Ellipse.Fill working correctly. I do have (single) Binding working correctly, plus MultiBinding on Ellipse.Tooltip:
<Ellipse Margin="210,56,0,0" Fill="{Binding InspectorPC, Converter={StaticResource statusButtonConverter}, Mode=OneWay}">
<Ellipse.ToolTip>
<MultiBinding Converter="{StaticResource statusStringConverter}" Mode="OneWay">
<Binding Path="InspectorPC"/>
<Binding Path="InspectorPCPing"/>
<Binding Path="InspectorPCReadHD"/>
</MultiBinding>
</Ellipse.ToolTip>
</Ellipse>
but I would like something like:
<Ellipse Margin="210,56,0,0">
<Ellipse.Fill>
<MultiBinding Converter="{StaticResource statusButtonConverter}" Mode="OneWay">
<Binding Path="InspectorPCPing"/>
<Binding Path="InspectorPCReadHD"/>
</MultiBinding>
</Ellipse.Fill>
<Ellipse.ToolTip>
<MultiBinding Converter="{StaticResource statusStringConverter}" Mode="OneWay">
<Binding Path="InspectorPC"/>
<Binding Path="InspectorPCPing"/>
<Binding Path="InspectorPCReadHD"/>
</MultiBinding>
</Ellipse.ToolTip>
</Ellipse>
(Obviously statusButtonConverter would need to be changed from IValueConverter to IMultiValueConverter, but that is not the issue.)
If this isn't working, it suggests a problem in your statusButtonConverter implementation.
A simple example shows no problem applying a MultiBinding to Ellipse.Fill:
<Window x:Class="WpfTest.FillMultiBinding"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfTest2"
Width="320"
Height="160">
<Window.Resources>
<l:BrushPartsConverter x:Key="brushPartsConverter" />
</Window.Resources>
<Window.DataContext>
<l:FillViewModel />
</Window.DataContext>
<Ellipse>
<Ellipse.Fill>
<!-- Dodger + Blue = DodgerBlue -->
<MultiBinding Converter="{StaticResource brushPartsConverter}" Mode="OneWay">
<Binding Path="Part1" />
<Binding Path="Part2" />
</MultiBinding>
</Ellipse.Fill>
</Ellipse>
</Window>
public class FillViewModel
{
public string Part1 => "Dodger";
public string Part2 => "Blue";
}
public class BrushPartsConverter : IMultiValueConverter
{
private static readonly BrushConverter InnerConverter = new BrushConverter();
public object Convert(object[] values, Type type, object p, CultureInfo c)
{
if (values?.Length == 2)
return InnerConverter.ConvertFrom("" + values[0] + values[1]);
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] types, object p, CultureInfo c)
{
return new[] { DependencyProperty.UnsetValue };
}
}
Post the code for your converter and binding context (view model), and we'll see what we can do.

WPF. Changing CheckBox IsChecked through MultiBinding doesn't triger CheckBox Command

I have Datagrid in my WPF app view where I use check boxes in row headers.
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<Grid >
<CheckBox BorderThickness="0"
Command="{Binding DataContext.AssignPartsToGroupCommand, RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}}"
>
<CheckBox.CommandParameter>
<MultiBinding Converter="{StaticResource PartsGroupAssignConverter}">
<Binding Path="IsChecked" RelativeSource="{RelativeSource Self}" Mode="OneWay"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType={x:Type DataGridRow}}"
Path="DataContext" Mode="OneWay"/>
</MultiBinding>
</CheckBox.CommandParameter>
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource PartsGroupAssignedConverter}" Mode="OneWay">
<Binding ElementName="partsGroupGrid" Path="SelectedItem.id"></Binding>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType={x:Type DataGridRow}}"
Path="DataContext" Mode="OneWay"/>
<Binding Path="IsSelected" Mode="OneWay"
RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type DataGridRow}}"
/>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</Grid>
</DataTemplate>
As you can see I bind CheckBox property "IsSelected" to multiple values and one of them is DataGrid row selection:
<Binding Path="IsSelected"
Mode="OneWay"
RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type DataGridRow}}"
/>
My problem is - the command linked to CheckBox is not triggered when I check CheckBox by selecting row. But it triggers when I do it manually (with mouse). How can I solve this?
According to the CheckBox source codes, the desired approach is not supported - a command will be called only after clicking on it.
However, you can create a small CheckBox descendant to achieve the behavior you need. This descendant will track changes of the CheckBox.IsChecked property and execute a command.
You can do it like this:
public class MyCheckBox : CheckBox {
static MyCheckBox() {
IsCheckedProperty.OverrideMetadata(typeof(MyCheckBox), new FrameworkPropertyMetadata((o, e) => ((MyCheckBox)o).OnIsCheckedChanged()));
}
readonly Locker toggleLocker = new Locker();
readonly Locker clickLocker = new Locker();
void OnIsCheckedChanged() {
if (clickLocker.IsLocked)
return;
using (toggleLocker.Lock()) {
OnClick();
}
}
protected override void OnToggle() {
if (toggleLocker.IsLocked)
return;
base.OnToggle();
}
protected override void OnClick() {
using (clickLocker.Lock()) {
base.OnClick();
}
}
}
And the Locker class:
class Locker : IDisposable {
int count = 0;
public bool IsLocked { get { return count != 0; } }
public IDisposable Lock() {
count++;
return this;
}
public void Dispose() { count--; }
}

Observation collection not updating when multibinding

I have a static ObservableCollection MarketList in ViewModel and
it is bound to Table in following manner:
<FlowDocumentScrollViewer Height="216" VerticalAlignment="Top" Margin="-7,2,7,0" >
<FlowDocument>
<Table CellSpacing="0" Name="MBPTable" >
<Table.DataContext>
<MultiBinding UpdateSourceTrigger="Explicit" Mode="TwoWay" Converter="{StaticResource indexerConverter}">
<Binding Path="MarketList" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True" Mode="TwoWay" BindsDirectlyToSource="True" />
<Binding Path="MBPindex" Mode="TwoWay" />
</MultiBinding>
</Table.DataContext>
<Table.Resources>
<local:IndexerConverter x:Key="indexerConverter"></local:IndexerConverter>
</Table.Resources>
Table contains ListView which is bound to Property of MarketList.
<ListView Name="MarketByPriceList" Width="300" ItemsSource="{Binding MarketByPriceList, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" AlternationCount="2" Margin="0,15,0,0" >
<ListView.View>
<GridView>
<GridViewColumn Header="Orders" Width="48" DisplayMemberBinding="{Binding MBP_NoofBuy_Orders, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" ></GridViewColumn>
<GridViewColumn Header="Bid Qty" Width="48" DisplayMemberBinding="{Binding MBPBID_Qty, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" ></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
This Is Convertor Method
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values != null && values[0] != DependencyProperty.UnsetValue)
{
// ObservableCollection<GlobalItems.Model.MarketWatchModel> allNames = (ObservableCollection<GlobalItems.Model.MarketWatchModel>)values[0];
int index = (int)values[1];
return GlobalItems.ViewModel.MarketWatchModelView.MarketList[index];
}
else
{
return null;
}
}
Binding works fine but update in collection is not reflected in UI
This appears to boil down to the same problem described here, and essentially the same solution should work.
Updating (adding to or removing from) the ObservableCollection does not cause your MultiBinding to refresh because the MultiBinding is listening for a PropertyChanged event and updating an ObservableCollection only fires a CollectionChanged event. A clean workaround is to add a Binding to the Count property of your ObservableCollection in your MultiBinding. E.g.
<MultiBinding UpdateSourceTrigger="Explicit" Mode="TwoWay" Converter="{StaticResource indexerConverter}">
<Binding Path="MarketList" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True" Mode="TwoWay" BindsDirectlyToSource="True" />
<Binding Path="MBPindex" Mode="TwoWay" />
<Binding Path="MarketList.Count" /> <!-- Additional Binding causes refresh when MarketList is updated -->
</MultiBinding>
Add the additional binding after all of your existing bindings so that the MultiValueConverter can just ignore the extra value. When the Count property of your ObservableCollection changes (when you add or remove items), then it will fire a PropertyChanged event and your MultiBinding will refresh.
I was troubleshooting the same issue, but needing it to fire not only when the count changed, but also when items were modified. Both Dan's answer and the one he linked got me pointed in the right direction, but along the way I discovered there is a better alternative to binding to the Count property. Bind to "Item[]" instead, which will fire a PropertyChange both when items are added or removed (same as Count), but also when items are rearranged or changed.
Updated version of Dan's example:
<MultiBinding UpdateSourceTrigger="Explicit" Mode="TwoWay" Converter="{StaticResource indexerConverter}">
<Binding Path="MarketList" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True" Mode="TwoWay" BindsDirectlyToSource="True" />
<Binding Path="MBPindex" Mode="TwoWay" />
<Binding Path="MarketList.Item[]" /> <!-- This is the difference -->
</MultiBinding>
My source was the source code of ObservableCollection, found here: http://referencesource.microsoft.com/#System/compmod/system/collections/objectmodel/observablecollection.cs

How does FallbackValue work with a MultiBinding?

I ask because it doesn't seem to work.
Assume we're binding to the following object:
public class HurrDurr
{
public string Hurr {get{return null;}}
public string Durr {get{return null;}}
}
Well, it would appear that if we used a MultiBinding against this the fallback value would be shown, right?
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} to the {1}"
FallbackValue="Not set! It works as expected!)">
<Binding Path="Hurr"/>
<Binding Path="Durr"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
However the result is, in fact, " to the ".
Even forcing the bindings to return DependencyProperty.UnsetValue doesn't work:
<TextBlock xmnlns:base="clr-namespace:System.Windows;assembly=WindowsBase">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} to the {1}"
FallbackValue="Not set! It works as expected!)">
<Binding Path="Hurr"
FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" />
<Binding Path="Durr"
FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Tried the same with TargetNullValue, which was also a bust all the way around.
So it appears that MultiBinding will never ever use FallbackValue. Is this true, or am I missing something?
A little more messing around and I found that a converter can return the UnsetValue I need:
class MultiValueFailConverter : IMultiValueConverter
{
public object Convert(
object[] values,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
if (values == null ||
values.Length != 2 ||
values.Any(x=>x == null))
return System.Windows.DependencyProperty.UnsetValue;
return values;
}
public object[] ConvertBack(
object value,
Type[] targetTypes,
object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException("Too complex hurt brain.");
}
}
However, this seems like a dirty filthy hack. I'd think a scenario like this would be accounted for in the framework. I can't find anything in Reflector, however.
This is a bit of an old question, but it could use some explanation.
From the FallbackValue documentation:
A binding returns a value successfully if:
The path to the binding source resolves successfully.
The value converter, if any, is able to convert the resulting value.
The resulting value is valid for the binding target (target) property.
If 1 and 2 return DependencyProperty.UnsetValue, the target property
is set to the value of the FallbackValue, if one is available. If
there is no FallbackValue, the default value of the target property is
used.
In the example provided, the binding successfully resolves to the Hurr and Durr properties. Null is valid value for a string which means the binding is valid.
In other words, the FallbackValue is used when the binding is unable to return a value and in the example provided, the binding does provide a valid value.
Take for example each of the following snippets that are based off the original example:
Example 1
The Hurr and Durr properties are bound correctly; null is a valid value and the FallbackValue will never be seen.
<TextBlock>
<TextBlock.Text>
<MultiBinding FallbackValue="Binding is valid. I will never be seen." StringFormat="{}{0} to the {1}">
<Binding Path="Hurr" />
<Binding Path="Durr" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Example 2
The Hurr and Durr properties are not bound correctly; the FallbackValue will be seen.
<TextBlock>
<TextBlock.Text>
<MultiBinding FallbackValue="Binding paths are invalid. Look at me." StringFormat="{}{0} to the {1}">
<Binding Path="xHurr" />
<Binding Path="xDurr" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Example 3
If one binding path is invalid, then the FallbackValue will be seen.
<TextBlock>
<TextBlock.Text>
<MultiBinding FallbackValue="One binding path is invalid. Look at me." StringFormat="{}{0} to the {1}">
<Binding Path="xHurr" />
<Binding Path="Durr" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Example 4
As with previous examples, the binding is correct, so the FallbackValue will not be used. Further, the FallbackValue for each of the child Binding properties of the MultiBinding parent should refer to a FallbackValue to be used for the target property of the MultiBinding, not for the child Bindings.
<TextBlock xmlns:base="clr-namespace:System.Windows;assembly=WindowsBase">
<TextBlock.Text>
<MultiBinding FallbackValue="Binding is valid. I will never be seen." StringFormat="{}{0} to the {1}">
<Binding FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" Path="Hurr" />
<Binding FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" Path="Durr" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Example 5
The binding is still valid even though a path is not provided in Binding properties since the binding will use whatever object it is bound to.
<TextBlock xmlns:base="clr-namespace:System.Windows;assembly=WindowsBase">
<TextBlock.Text>
<MultiBinding FallbackValue="Binding is still valid. I will never be seen." StringFormat="{}{0} to the {1}">
<Binding FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" />
<Binding FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Example 6
Finally, if a converter is added to any of the Binding properties to force an UnsetValue, then the MultiBinding FallbackValue will be seen:
Converter
internal class ForceUnsetValueConverter : IValueConverter
{
#region Implementation of IValueConverter
public object Convert( object value, Type targetType, object parameter, CultureInfo culture )
{
return DependencyProperty.UnsetValue;
}
public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
{
throw new NotImplementedException();
}
#endregion
}
XAML
<TextBlock>
<TextBlock.Text>
<MultiBinding FallbackValue="Binding is valid, but look at me. I'm an UnsetValue." StringFormat="{}{0} to the {1}">
<Binding Converter="{StaticResource ForceUnset}" Path="Hurr" />
<Binding Path="Durr" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>

Resources