Pass control to binding by value - wpf

I have a converter that I use to determine the size of a child element based on the size of its container (parent).
<Setter Property="Width" Value="{Binding ActualWidth, ElementName=rowsContainer,
Converter={StaticResource sizer}}"/>
public class CellSizeConverter : IValueConverter
{
private readonly int _maxNum = 7;
private readonly int _margin = 0;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return null;
}
double width = (double)value;
return (width / _maxNum) - _margin;
}
}
This works, and when I resize my window, the size of the components change. Hooray!
I want to factor in the height as well, so I've tried passing in the control itself and getting its properties inside the converter:
<Setter Property="Width" Value="{Binding ElementName=rowsContainer, Converter={StaticResource sizer}}"/>
public class CellSizeConverter : IValueConverter
{
private readonly int _maxNum = 7;
private readonly int _margin = 0;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return null;
}
ItemsControl rowsContainer = (ItemsControl)value;
return (rowsContainer.ActualWidth / _maxNum) - _margin;
}
}
However, when I do this the size never changes. It seems like the control gets passed to the converter once but never updates. I imagine it has something to do with passing by reference rather than passing by value. Or something like that.
I know I can pass multiple values into the converter using a MultiBinding and an IMultiValue Converter but since all that values I'd want are already wrapped up in a nice little object (the control) it would be so much cleaner in my xaml to just bind it this way.
How can I pass the control such that it updates?

The converter only gets called when the property that you actually bind to is set to a new value.
This means that you cannot bind to the rowsContainer control itself as it never gets when the ActualWidth or ActualHeight property is updated.
A MultiBinding to both ActualWidth or ActualHeight is the way to go here.
How can I pass the control such that it updates?
You can't for the reason already mentioned.

It seems like the control gets passed to the converter once but never updates. I imagine it has something to do with passing by reference rather than passing by value.
Property updates in WPF aren't magic, code has to explicitly call into WPF to tell it a value has been updated.
When you bind ActualWidth, that's actually a dependency property that has a changed event WPF can hook into and be notified by the code changing it. However, when you bind the control itself, that is not a dependency property, and the control doesn't implement INotifyPropertyChanged, so WPF never knows its properties have changed.
I know I can pass multiple values into the converter using a MultiBinding and an IMultiValue Converter but [...]
A MultiBinding allows you to bind to multiple dependency properties and have WPF listen to all their property change notifications at once. I stopped the quote at "but" because it doesn't matter what comes after, that's what you need to do.

Related

How to know if binding has no target in case an object doesn't have that property

I have some classes. Some of them has Color Property, but one does not. I'm using same ListBox User Control for them. I want to hide ColorPicker for those classes that have no such property. I know, I can do a workaround and hide it if DataContext is of certain type, but I want to know if there is a way to check if the binding target isn't just null at a moment, but doesn't exist at all.
I used the proposed converter (returning true/false) with no result, but #mm8 proposal to set FallbackValue to false worked well.
You could specify a FallbackValue for a specific binding that the target property will be set the when the source property is not found: https://msdn.microsoft.com/en-us/library/system.windows.data.bindingbase.fallbackvalue(v=vs.110).aspx
Use a ValueConverter for the binding, and in the Convert method, check for UnsetValue:
<FrameworkElement Property="{Binding SomeProperty, Converter={StaticResource BindingExists}/>
and
public class BindingExists : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == DependencyProperty.UnsetValue)
{
// perhaps do something
return Binding.DoNothing;
}
else if (value == null)
{
// perhaps do something else
}
return value
}
// ...
You can then use DataTriggers to display different templates or whatever, in case of null vs. non-existent value.

TwoWay Binding MySettings to DataGrid Column Width

I am trying to bind the width of my columns within a DataGrid to an application settings property. I have this working when binding is set to OneWay mode however, I need the setting to be updated based on the width of the column when the app closes. When I change the binding mode to TwoWay, the binding breaks all together. My code is below, how can I go about achieving this?
Extension Class
Public Class SettingBindingExtension
Inherits Binding
Public Sub New()
Initialize()
End Sub
Public Sub New(ByVal path As String)
MyBase.New(path)
Initialize()
End Sub
Private Sub Initialize()
Me.Source = MySettings.[Default]
'OneWay mode works for the initial grid load but any resizes are unsaved.
Me.Mode = BindingMode.OneWay
'using TwoWay mode below breaks the binding...
'Me.Mode = BindingMode.TwoWay
End Sub
End Class
xaml
xmlns:w="clr-namespace:Stack"
<DataGrid>
...
<DataGridTextColumn Header="STACK"
Width="{w:SettingBinding StackColumnWidth}"/>
...
</DataGrid>
The problem is that Width is of type DataGridLength and there is no default converter back to double so you will need to create your own converter to do that, here is an example of converter which should work:
class LengthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
DataGridLengthConverter converter=new DataGridLengthConverter();
var res = converter.ConvertFrom(value);
return res;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
DataGridLength length = (DataGridLength)value ;
return length.DisplayValue;
}
}
Thanks for the responses, it was a datatype issue. Instead of using a converter, I just changed the datatype of the setting over to DataGridLength. Nothing else was changed and everything functions as it should. Thanks again.
The DataGridTextColumn.Width property can definitely handle a Two Way Binding, so I can only assume that your custom Binding object is causing this problem. You said that the Binding broke, but you didn't tell us what the error was. As a simple test, try replacing it with the standard Binding class:
<DataGridTextColumn Header="STACK" Width="{Binding StackColumnWidth}" />
One other thing to note is that on the DataGridColumn.Width Property page at MSDN, it says:
The DisplayValue of the Width property is constrained by the following properties, if they are set, in order of precedence:
•
DataGridColumn.MaxWidth
•
DataGrid.MaxColumnWidth
•
DataGridColumn.MinWidth
•
DataGrid.MinColumnWidth
Therefore, you may need to make sure that these are set to appropriate values. However, this is not causing your problem.
If you still can't get any resolution, you can try manually saving the value as the application closes if you have a reference to the DataGrid control:
int index = dataGrid.Columns.Single(c => c.Header == "STACK").DisplayIndex;
double width = dataGrid.Columns[index].Width;

Pass view to viewmodel with datatemplate

I have a window named ParameterEditorView with a ParameterEditorViewModel as DataContext. In the ParameterEditorViewModel I have a list of ParameterViewModel. In the ParameterEditorView I have an ItemsControl whose ItemsSource is binded to the list of ParameterViewModel in the ParameterEditorViewModel. I need the ParameterViewModel to have a reference to the ParameterView (more on that later). In the Resources section of the ParameterEditorView I add the DataTemplate:
<DataTemplate DataType="{x:Type my:ParameterViewModel}" >
<my:ParameterView HorizontalAlignment="Left"/>
</DataTemplate>
So, how can I pass a reference of the ParameterView that is created to show the ParameterViewModel to it?
The reason I need the ParameterView in the ParameterViewModel is the following:
I have a TextBox whose Text property is binded to the PropertyModelView.Name property. But I want to display a default string when the Name is empty or Null. I've tried to set the property value to the default string I want when that happens but the TextBox.Text is not set in this scenario. I do something like this:
private string _name;
public string Name
{
get { return _name; }
set
{
if (value == null || value.Length == 0)
Name = _defaultName;
else
_name = value;
}
}
I've also tried to specifically set the TextBox.Text binding mode to TwoWay without success.
I think this is a defense mechanism to prevent an infinite loop from happening but I don't know for sure.
Any help on this front would also be highly appreciated.
Thanks,
José Tavares
{Binding } has a FallbackValue, btw.
Your question, it confuses me. I'd assume your PVM has a collection of PV's as a public property, which is bound within the UI. Also, I think you're mixing terms. Its Model-View-ViewModel where the ViewModel is the DataContext of the View, and the Model is exposed by the ViewModel via a public property. Sounds like if you're binding the window to a collection of ViewModels they are actually Models. It may seem pedantic, but getting your terms correct will help you research and ask questions.
Another solution would be to add a Converter to your Binding in combination with FallbackValue (I've had to do this, IIRC). That converter would be an IValueConverter that returns "DependencyProperty.UnsetValue" if the string is null or empty. I think this works sometimes because the TextBox will set the bound property to the empty string rather than null if the TB is empty. Here's a little sample to whet your whistle (not guaranteed to work; you need to debug this and tweak it):
public class ThisMightWorkConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
var temp = value as string;
if(string.IsNullOrWhiteSpace(temp))
return System.Windows.DependencyProperty.UnsetValue;
return temp;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return value; // you might need to change this
}
}

WPF Property Data binding to negate the property

Is there any way to change the value of property at runtime in WPF data binding. Let's say my TextBox is bind to a IsAdmin property. Is there anyway I can change that property value in XAML to be !IsAdmin.
I just want to negate the property so Valueconverter might be an overkill!
NOTE: Without using ValueConverter
You can use an IValueConverter.
[ValueConversion(typeof(bool), typeof(bool))]
public class InvertBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool original = (bool)value;
return !original;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
bool original = (bool)value;
return !original;
}
}
Then you'd setup your binding like:
<TextBlock Text="{Binding Path=IsAdmin, Converter={StaticResource boolConvert}}" />
Add a resource (usually in your UserControl/Window) like so:
<local:InvertBooleanConverter x:Key="boolConvert"/>
Edit in response to comment:
If you want to avoid a value converter for some reason (although I feel that it's the most appropriate place), you can do the conversion directly in your ViewModel. Just add a property like:
public bool IsRegularUser
{
get { return !this.IsAdmin; }
}
If you do this, however, make sure your IsAdmin property setter also raises a PropertyChanged event for "IsRegularUser" as well as "IsAdmin", so the UI updates accordingly.
If you specifically want to do this at XAML end (I am not sure the reason for that, unless you have 100s of similar operation of negate) there are only two ways 1) Using IValueConverter 2)write a XAML Markup Extension (Way too much work for this small task :))
Then the other obvious way is to write another property in your ViewModel , which can return the Negative of the IsAdmin property.
You can't bind to !Property, but you could create a new Binding with an appropriate IValueConverter and change out the entire Binding at runtime. The key is the BindingOperations class, which allows you to change the binding on a particular DependencyProperty.
public static void InvertBinding(DependencyObject target, DependencyProperty dp)
{
//We'll invert the existing binding, so need to find it
var binding = BindingOperations.GetBinding(target, dp);
if (binding != null)
{
if (binding.Converter != null)
throw new InvalidOperationException("This binding already has a converter and cannot be inverted");
binding.Converter = new InvertingValueConverter(); //This would be your custom converter
//Not sure if you need this step, but it will cause the binding to refresh
BindingOperations.SetBinding(target, dp, binding);
}
}
This should give you a general idea; I wouldn't use this for production code, as you'd probably want to generalize it to toggle the converter or whatever else you need to change out at runtime. You could also avoid changing the binding entirely by creating a new property you bind to that encapsulates this 'switching' logic. The last option is probably the best.
You can write a ValueConverter that automatically negates the input before returning it. Have a look at BenCon's blog for a short reading on value converters.

What causes a Value Converter to fire?

I am setting an IsEnabled property of a control based on whether or not a SelectedIndex >= 0 in a ListBox. I can do this in the code behind, but I wanted to create a value converter for this behavior since it is something I do frequently.
I created this Value Converter to handle the task and bound it to the IsEnabled property:
[ValueConversion(typeof(Selector), typeof(bool))]
public class SelectorItemSelectedToBooleanConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null || !(value is Selector))
return null;
var control = value as Selector;
return control.SelectedIndex >= 0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
The Converter is only called once, when the application is loaded. It does not fire when the SelectedIndex changes.
My question is therefore what causes a Value Converter to fire? I assume it is when the bound data changes, so is there a way to force the converter to fire in different circumstances? Am I even asking the right question?
It won't fire because you've bound it to the Selector itself, not the SelectedIndex property of the Selector. WPF will monitor every property in the path you bind to, and update values if any of those properties changes. The Selector isn't changing, the SelectedIndex is.
I think a converter might be the wrong way to go about this. A better solution would be to use a RoutedCommand, and the command's CanExecuted method checks to see if your SelectedIndex is greater than or equal to 0.
That much being said, if you still want to use your value converter, you should know that the converter fires whenever the binding source updates. You can change the behavior of the update using the UpdateSourceTrigger property on the Binding. By default this is set to PropertyChanged, but for textboxes it is set to LostFocus (whenever the textbox loses focus the binding is updated).

Resources