Multibinding not working - StrokeThickness of paths is unchanged - c#

I have an ItemsControl with ItemsSource filled by an ObservableCollection of Paths. The Path class that implements INotifyPropertyChanged has a property named StrokeThickness :
private double _strokeThickness;
public double StrokeThickness
{
get { return _strokeThickness; }
set
{
_strokeThickness = value;
OnPropertyChanged(nameof(StrokeThickness));
}
}
In our ViewModel we have :
public ObservableCollection<Path> PathCollection
{
get { return _pathCollection; }
set
{
_pathCollection = value;
OnPropertyChanged(nameof(PathCollection));
}
}
And this is my View :
<!-- Paths -->
<ItemsControl ItemsSource="{Binding PathCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Stroke="{Binding Stroke}"
Data="{Binding Data}">
<Path.StrokeThickness>
<MultiBinding Converter="{StaticResource
CorrectStrockThiknessConvertor}">
<MultiBinding.Bindings>
<Binding Source="{Binding
StrokeThickness}"></Binding>
<Binding ElementName="RootLayout"
Path="DataContext.ZoomRatio" >
</Binding>
</MultiBinding.Bindings>
</MultiBinding>
</Path.StrokeThickness>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="Canvas"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
I have used a Multibinding Convertor to give me true StrokeThickness based on ZoomRatio. ZoomRatio is being calculated every time map zoomed.
This is my MultiBinding converter :
public class CorrectStrockThiknessConvertor : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values != null & values.Length == 2 && (double)values[0] != 0)
{
return (double)values[1] / (double)values[0];
}
return 1;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
when I trace this Converter its work fine but every time it's entry data for StrokeThikness is same and it means return value doesn't change StrokeThikness of Paths.
Am I doing something to wrong?

The first Binding in the MultiBinding is wrong. It should look like this:
<MultiBinding.Bindings>
<Binding Path="StrokeThickness" />
<Binding ElementName="RootLayout" Path="DataContext.ZoomRatio" />
</MultiBinding.Bindings>
Also check if the property name is correct, as you constantly write StrokeThikness instead of StrokeThickness in your question.
You should also check your converter code. It seems you are dividing ZoomRatio by StrokeThickness, which to my understanding should be the other way round.

Related

WPF Label content shows DependencyProperty.UnsetValue in design mode

I have an WPF label and I have bound some data into a string using StringFormat from xaml:
<Label Grid.Row="0" Grid.Column="1" Style="{StaticResource MyLblResource}">
<Label.Content>
<TextBlock VerticalAlignment="Center">
<TextBlock.Text>
<MultiBinding StringFormat="{}({0}) {1}">
<Binding Path="MyDataModel.Id" />
<Binding Path="MyDataModel.Desc" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Label.Content>
</Label>
Above code works fine, no problems but in design time, in xaml view, in the TextBlock content it is shown:
{{DependecyProperty.UnsetValue}} {{DependencyProperty.UnsetValue}}
Why is this being shown instead of being shown as empty? Is there any way to show this as empty?
This should do the trick:
public class StringFormatConverter : MarkupExtension, IMultiValueConverter
{
public string StringFormat { get; set; } = #"({0}) {1}";
public string PlaceHolder { get; set; } = "Empty";
public override object ProvideValue(IServiceProvider serviceProvider) => this;
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return string.Format(StringFormat, GetValues(values));
}
private IEnumerable<string> GetValues(object[] values)
{
foreach (var value in values)
yield return value == DependencyProperty.UnsetValue || value == null ? PlaceHolder : value.ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new[] { Binding.DoNothing, Binding.DoNothing };
}
}
Use it like this:
<MultiBinding Converter="{converter:StringFormatConverter PlaceHolder=MyPlaceHolderText}">
<Binding Path="MyDataModel.Id" />
<Binding Path="MyDataModel.Desc" />
</MultiBinding>
Please be aware that you can only set static values in StringFormat and PlaceHolder - because they are no DependencyProperty.

WPF Binding to a function return

I have a "Checked ListBox", a ListBox with a CheckBox for each of my Items.
In this ListBox I have a List of Players.
<ListBox ItemsSource="{Binding MyPlayers}" SelectedItem="{Binding SelectedPlayer}" Margin="69,51,347.4,161.8">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem>
<CheckBox IsChecked="{Binding ???}" Content="{Binding Path=Pseudo}" />
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel :
public class MainWindowViewModel : BaseViewModel
{
SavingContext MyDatabaseContext;
public ObservableCollection<Player> MyPlayers
{
get
{
return MyDatabaseContext.MyPlayers.Local;
}
}
Tournament _MyTournament;
public Tournament MyTournament
{
get
{
return _MyTournament;
}
set
{
_MyTournament = value;
}
}
public MainWindowViewModel(Tournament myTournament)
{
MyDatabaseContext = new SavingContext();
MyDatabaseContext.MyPlayers.Load();
MyTournament = myTournament;
}
}
I pass in my ViewModel a Tournament, that contains an HashSet of Players called Participants.
I would like to bind my IsChecked properties to the result of MyTournament.Participants.Contains(this) with this being the Player related to the CheckBox. But I can't manage to make it works.
Edit :
I tried to use a Converter, but with no success.
<helper:PlayerToTournamentRegistered x:Key="PlayerToTournamentRegistered" />
<ListBoxItem>
<CheckBox IsChecked="{Binding Converter={StaticResource PlayerToTournamentRegistered}}" Content="{Binding Path=Pseudo}" />
</ListBoxItem>
public class PlayerToTournamentRegistered : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//Temp test to see if it would work
if (value == null) return false;
return true;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I get an error each time Provide value on 'System.Windows.Data.Binding' threw an exception
Any Advices ?
So I managed to get it working using a MultiConverter.
The first error that I had was due to the fact that the Mode is by Default Two Way, I only want to have my Converter work OneWay.
I used a MultiBinding to send to my Converter both my ListBoxItem and my Tournament.
My Converter return true or false, and is linked to my CheckBox.IsChecked properties.
List Box :
<ListBox ItemsSource="{Binding MyPlayers}" SelectedItem="{Binding SelectedPlayer}" Margin="69,51,347.4,161.8">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem>
<CheckBox Content="{Binding Path=Pseudo}" >
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource PlayerToTournamentRegistered}" Mode="OneWay">
<Binding />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=Window}" Path="DataContext.MyTournament"/>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Converter :
public class PlayerToTournamentRegistered : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if(!(values[0] is Student) || !(values[1] is Tournament))
{
return false;
}
Tournament myTournament = (Tournament)values[1];
Student myPlayer = (Student)values[0];
if (myTournament.Participants.Contains(myPlayer))
return true;
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}

Apply conversion to all elements in a ComboBox drop down menu

I am trying to change the text only for contents of all items in a ComboBox based on a specific property in the ViewModel. I’ve created a DataTemplate with the Binding values as SelectedValue and the specific property I want to base the conversion on SomeProperty:
<ComboBox ItemsSource="{Binding Path=ChannelValues}"
SelectedValue="{Binding Path=Channel, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource ResourceKey=ChannelNumConverter}">
<Binding Path="SelectedValue"
RelativeSource="{RelativeSource AncestorType={x:Type ComboBox}}" />
<Binding Path="DataContext.SomeProperty"
ElementName="DataContextView" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This appears to work, expect all the values in the drop down get changed to the translated SelectedValue. I’ve tried replacing SelectedValue with Text, but that doesn’t work either. Is there a way to apply this conversion to all values in the drop down (again, only changing the displayed values, not the underlying data)?
Update - ViewModel
// Populate somewhere with values
private ObservableCollection<ushort> mChannelValues = new ObservableCollection<ushort>();
public ObservableCollection<ushort> ChannelValues
{
get
{
return mChannelValues;
}
}
private ushort mChannelNum;
public ushort Channel
{
get
{
return mChannelNum;
}
set
{
if (mChannelNum != value)
{
mChannelNum = value;
OnPropertyChanged(new PropertyChangedEventArgs("Channel"));
}
}
}
private ushort mSomeProperty;
public ushort SomeProperty
{
get
{
return mSomeProperty;
}
set
{
if (mSomeProperty!= value)
{
mSomeProperty= value;
OnPropertyChanged(new PropertyChangedEventArgs("SomeProperty"));
}
}
}
Update 2 - Simple Converter
public object Convert(
object[] values,
Type targetType,
object parameter,
CultureInfo culture)
{
if (targetType != typeof(string))
throw new InvalidOperationException("The target must be a string");
if ((values[0] != null) && (!(values[0] is ushort)))
throw new InvalidOperationException("The channel must be an short");
if ((values[1] != null) && (!(values[1] is ushort)))
throw new InvalidOperationException("The some property must be a ushort");
ushort ushort_val = ushort.Parse((string)values[0]);
ushort ushort_some_property = ushort.Parse((string)values[1]);
switch (ushort_some_property)
{
case 0:
return (ushort_val + 1).ToString();
case 1:
return (ushort_val + 7).ToString();
case 2:
return (ushort_val + 2).ToString();
default:
return ushort_val.ToString();
}
}
Instead of using a MultiBinding, you could use SomeProperty as a ConverterParameter
<TextBlock Text="{Binding Converter={StaticResource ResourceKey=ChannelNumConverter}, ConverterParameter={Binding SomeProperty}}"/>
In converter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var someProperty = parameter as SomeType;
...
The problem is in fact that your itemTemplate is applied to ALL! Items in the combobox, whose converter actually processes the currently selected item and someproperty leading to equal values in all items.
The approach at this point is not the problem. You just have to bind the current text's value in the viewmodel instead of the selected value.
This will combine two values from your viewmodel into the result displayed in the textbox without any recursive updates.
Finally figured out how to do this. Below is the xaml to apply the converter to all elements in the drop down list:
<ComboBox ItemsSource="{Binding Path=ChannelValues}"
SelectedValue="{Binding Path=Channel, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource ResourceKey=ChannelNumConverter}">
<Binding />
<Binding Path="DataContext.SomeProperty"
RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType=local:DataContextView}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Custom Sort in XamDataGrid for multibinding

I have a multiBinding which works great,
I want to be able to sort a certain column, it displays w:width, h:heightAs far as I understand I need to build a custom IComparer class which will to the comparison.
Here is my XAML
<igWPF:UnboundField Label="Output
Width/Height" Width="auto">
<igWPF:Field.Settings>
<igWPF:FieldSettings SortComparer="{StaticResource SortWidthHeightComparer }">
<igWPF:FieldSettings.CellValuePresenterStyle>
<Style TargetType="{x:Type igWPF:CellValuePresenter}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type igWPF:CellValuePresenter}" >
<TextBlock Margin="3">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource settingsBdsToStringConverter}">
<Binding Path="DataItem.Key"/>
<Binding Path="DataContext.SelectedPipeMode" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type igWPF:XamDataGrid}}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</igWPF:FieldSettings.CellValuePresenterStyle>
</igWPF:FieldSettings>
</igWPF:Field.Settings>
</igWPF:UnboundField>
Here is my multibinding converter
class SettingsOutputResToStringConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[1] is Mode && values[0] is ConfigurationKey)
{
var pMode = (Mode)values[1];
var key = values[0] as ConfigurationKey;
var res = key.GetOutput(pMode);
return String.Format("W: {0}, H: {1}", res.Width, res.Height);
}
return String.Empty;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return null;
}
}
However my problem is how to I pass the multibind result into the Comparer class
class SortWidthHeightComparer : IComparer
{
public int Compare(object x, object y)
{
return 1;
}
}
object x and object y , are always null
Gilad,
I found this post:
http://www.infragistics.com/community/forums/t/17878.aspx
It seems like a simple solution.
I'm going to try the same approach for filtering records.

WPF - binding to a property in ViewModel and to other control

In my WPF application (using MVVM) I have a CheckBox and a TextBlock. When the CheckBox is checked the value from the TextBlock will be saved. There is a binding from both controls to my ViewModel. Below simplified XAML:
<StackPanel>
<Label>Add to list</Label>
<CheckBox IsChecked="{Binding Path=AddItem}"></CheckBox>
<Label>Gross amount:</Label>
<TextBlock Text="{Binding Path=Amount}"></TextBlock>
</StackPanel>
Now I would like to have the CheckBox checked when a user starts typing in the TextBlock. I know binding can do that but I already bind to a property in my ViewModel. How can I bind to a property in ViewModel and to other control?
You should use the multibinding. Something like this:
<CheckBox Content="CheckBox" HorizontalAlignment="Left" Margin="191,82,0,0" VerticalAlignment="Top">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource checkConverter}">
<Binding Path="IsChecked"/>
<Binding Path="UserStartedTyping"/>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
checkConverter is a MultiValueConverter that you need in order to decide what to do with the values you are binding with (such as &&, || etc.).
public class CheckConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return (bool)((bool)values[0] || (bool)values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
object[] splitValues = { value, false };
return splitValues;
}
}
UserStartedTyping is a property in the ViewModel that would be set to true when KeyDown event is fired.
Hope it helps.
You can try setting the AddItem to true when the user starts to change the amount value:
private string _amt;
public string Amount
{
get{return _amt;}
set
{
_amt = value;
if(AddItem == false)
AddItem = true;
//PropertyChanges here
}
}

Categories

Resources