Xaml binding for radio/checkbox to toggle between imperial/metric - wpf

Pretty much as the title states, I'm grabbing some values from a Db, which are all in Km, but I want to implement a converter which I can toggle between Miles or Kilometers, and want to bind which is displayed to either a checkbox, or a radio button group, whichever is easiest (Radio would be preferred).
I'm thinking I can just use an IValueConverter rather than an IMultiValueConverter, and the Convert/ConvertBack methods, as the default will be Kilometers, but I don't know how to call the ConvertBack method. Or I could pass true/false as the ConverterParameter depending on whether I want Km/Miles displayed.
But either way I'm not sure how to hook up the Xaml Binding on either method (I know how to do a standard value converter binding, but not the extra flumff needed.
Any hints appreciated.
<StackPanel Grid.Row="0" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right">
<RadioButton Content="Km" GroupName="rdBtnGrpValue" IsChecked="True" />
<RadioButton Content="Miles" GroupName="rdBtnGrpValue" />
</StackPanel>
And:
<TextBox HorizontalAlignment="Stretch" VerticalAlignment="Top" Grid.Column="1" Text="{Binding EquatorialCircumference, Converter={StaticResource KmMiConv}, StringFormat='{}{0:0,0.0}'}" />

If you are using the MVVM pattern, and are using a view-model as your DataContext, you could use a Mode=TwoWay binding between the RadioButtons and a boolean property in the view-model, something like bool ConvertToImperial { get; set; }.
Your actual conversion can occur in the getter for the EquatorialCircumference property. If ConvertToImperial is true, return the value in miles, otherwise return the value in kilometres.
Then, for the TextBox, you can simply bind it to the EquatorialCircumference property, and the value displayed will be in the selected unit.
You will, however, need to raise a property change notification for any properties whose values are affected by a change in units.

Related

WPF UserControl property binding for some and not others

I have a the following three items in my xaml that have there visibility changed depending on a boolean property located in the DataContext of the page.
<worklist:PhysicianQuickInsert Visibility="{Binding IsInsertingPhysician, Converter={StaticResource NullEmptyFalseToInvisibileConverter}}" />
<shared:LoadingIndicator Visibility="{Binding IsInsertingPhysician, Converter={StaticResource NullEmptyFalseToInvisibileConverter}}" />
<TextBlock Text="Test" Foreground="Red" Visibility="{Binding IsInsertingPhysician, Converter={StaticResource NullEmptyFalseToInvisibileConverter}}" />
The TextBlock and LoadingIndicator are becoming visible/collapsed, but the PhysicianQuickInsert fails to do anything.
I have verified that the getter of the boolean property is never retrieved for the PhysicianQuickInsert.
Why would this happen? PhysicianQuickInsert is a usercontrol, as is the LoadingingIndicator.
This can happen if you defined a new Visibility property on your UserControl. If you've done this, you may need to make sure that it's binding two-way by default, has a proper setter, etc.
Following the comments on my answer, this issue was that this user control binds to a another datacontext (different type) within it. I added a RelativeSource and it worked.
Thanks everyone!

XAML reference control and properties in x:Array

<RichTextBox x:Name="OrigText" Margin="0,0,8,0" d:LayoutOverrides="Width"/>
<Button x:Name="OrigFileBrowse" Command="{Binding BrowseCommand}" CommandParameter="{Binding ElementName=OrigText, Path=Document}" HorizontalAlignment="Center" Margin="0,0,8,2.442" Width="75" Content="Browse" Grid.Row="1" d:LayoutOverrides="Height"/>
<RichTextBox x:Name="ModifiedText" Grid.Column="1" Margin="8,0,0,0"/>
<Button x:Name="ModifiedFileBrowse" Command="{Binding BrowseCommand}" CommandParameter="{Binding ElementName=ModifiedText, Path=Document}" HorizontalAlignment="Center" Width="75" Content="Browse" Grid.Row="1" Grid.Column="1" Margin="0,0,0,2.442" d:LayoutOverrides="Height"/>
<Button x:Name="Compare" Command="{Binding CompareCommand}" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Content="Compare" Grid.Row="2" Grid.ColumnSpan="2">
<Button.CommandParameter>
<x:Array Type="RichTextBox">
<local:CompareTextView/>
</x:Array>
</Button.CommandParameter>
</Button>
Trying to get 2 items to be passed when the Compare button is clicked as it will then execute a compare command. Attempted to make use of MultiBinding however that is firing on instantiation and therefore the converter then fires accordingly. It does NOT fire when I click compare and the compare command is executed.
With that not working, I am attempting to now reference the controls within XAML to pass within an ArrayExtension. Not sure of the syntax or if it is even possible as I know you cannot bind within the ArrayExtension. The above fails since it can not construct a new CompareTextView view, which has no default constructor since I am making use of Prism...
Pretty frustrating, hopefully someone can help me out...
EDIT:
Want to clear some things up. The issue is not that I want CanExecute called again. The issue is that at instantiation of the controls, the converter is called and executed and the values are returned...but where they go I have no clue? The converter is never called again. If I could get the initial references to the FlowDocument this would all be a moot point...but it doesn't return things anywhere per se...since this is a command...if that makes sense...when making use of MultiBinding.
<Button x:Name="Compare" Command="{Binding CompareCommand}" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Content="Compare" Grid.Row="2" Grid.ColumnSpan="2">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource FlowDocumentConverter}">
<Binding ElementName="OrigText" Path="Document"/>
<Binding ElementName="ModifiedText" Path="Document"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
UPDATE:
Tried what refereejoe mentions here, scroll down a little bit to see his posting. While CanExecute continually fires, this does nothing to resolve the issue. In addition I switched the MultiBinding to be a single item, it is coming back null. Again when the converter fires on instantiation the FlowDocument references are there...
ANSWER:
Abe's mention that it was being cached led me to try something else. Since I knew that the FlowDocument references were available when the converter was called I knew they were there. Something was getting fouled up. The key piece appears to be in the converter itself. I was simply returning the object[]. Then when the command fired the arg was indeed an object[] but the two items were null. I created a class called Docs, which had two properties, one for each FlowDocument reference. When the converter fired I set the properties appropriately and then returned the Docs object. Now when I initiated the compare command, the Docs object was the args and it had the reference to the FlowDocuments just as I needed! Not sure if this is by design, but the fact that the items get lost when using the object[] doesn't make sense to me.
The proper way to do this is indeed with a MultiBinding on the CommandParameter. You won't see it call your CanExecute method unless WPF is informed that the method could return a different value than it had already cached (via the CanExecuteChanged event).
Since you are relying on the parameter passed in to determine this, we have to raise the event when the parameter changes. Since we can't really determine that in the command, we can use another technique: tell WPF to poll our command anytime it polls UICommands. This is done by implementing your ICommand like so:
public class MyCommand : ICommand
{
public void Execute(object parameter) { /* do stuff */ }
public bool CanExecute(object parameter { /* determine if we can do stuff */ }
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
Obviously, this prevents you from using the Prism DelegateCommand, but this will respond to changes in the command parameters.
UPDATE
Another thing to consider is that the Document property on the RichTextBox isn't actually changing. Instead, when you type into it, the content of the FlowDocument changes. Since the property instances don't change, the converter won't get fired again, and the originally converted value will get stored in the CommandParameter property.
One of the ways to force the converter to be called again is to add a Binding to the MultiBinding that is bound to a property that will change every time the text of the RichTextBox changes.
A somewhat hacky solution would be to use the IsKeyboardFocusWithin property, as that will mimic the default binding behavior of TextBox.Text (i.e. when the TextBox loses focus, the Binding updates):
<MultiBinding Converter="{StaticResource FlowDocumentConverter}">
<Binding ElementName="OrigText" Path="Document" />
<Binding ElementName="ModifiedText" Path="Document" />
<Binding ElementName="OrigText" Path="IsKeyboardFocusWithin" />
<Binding ElementName="ModifiedText" Path="IsKeyboardFocusWithin" />
</MultiBinding>
Obviously, in your converter, you will need to ignore these additional values, as they aren't relevant to your conversion.

Databinding Not Updating When Using {Binding .} or {Binding}

I have an ObservableCollection of addresses that I am binding to a ListBox. Then in the ItemTemplate I am Binding to the current address record using {Binding .}. This results in my addresses displaying using their ToString method which I have setup to format the address. All is good, except if I update properties on an individual address record the list in the UI does not update. Adds/Deletes to the list do update the UI (using the ObservableCollection behavior). If I bind directly to properties on the address the UI does update (using the INotifyPropertyChanged behavior of the Address object).
My question is, is there a way to notify the UI of the change to the object as a whole so that I can still use this syntax or do I need to punt and put a DisplayText property on my address type that calls the ToString method and bind to that? FYI, this is an MVVM architecture so I don't have the luxury of calling Refresh on the ListBox directly.
Thanks for any help/ideas.
<ListBox x:Name="AddressList" ItemsSource="{Binding Addresses}" Background="Transparent" BorderBrush="Transparent"
Width="200" HorizontalAlignment="Left">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding .}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When you bind to the Address object itself, the object itself -- that is, its identity -- doesn't change, even though its properties do. WPF therefore doesn't know to refresh the binding in this case.
So yes, you need to bind to a notifying property (or properties) rather than the whole object. As you say, one way to do this is to create a DisplayText property, and raise the PropertyChanged event for that property whenever something that affects the display text changes. Another is to use multiple TextBlocks in a horizontally oriented StackPanel, each bound to a particular property e.g.
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding HouseNumber}" />
<TextBlock Text=", " />
<TextBlock Text="{Binding Street}" />
<TextBlock Text=", " />
<TextBlock Text="{Binding City}" />
</StackPanel>
The advantage of the second approach is that it gives you flexibility in the UI to change how addresses are displayed, e.g. multiple lines, formatting, etc.; the downside is that it gets complicated if you have conditional logic e.g. an optional flat number or second address line.
I tried to reproduce the problem and succeeded.
I activated the step-into-.NET debugging options, and saw that WPF does not listen to INotifyPropertyChanged if the path in the binding is empty.
What worked to get a change to be reflected in the list box is to replace the whole object in the ObservableCollection. This triggers the INotifyCollectionChanged, with the Replace action.
But this may not be acceptable in your case. And it could be seen more like a hack than a solid solution.
I'd seriously consider having a DataTemplate for Address. There you should bind to the exact properties you need (which would create the listener for INotifyPropertyChanged). It is more flexible than ToString() and you may encounter cases where you have a need for ToString() to do something for non-UI stuff, which would create a conflict. And honestly, ToString is not really meant for UI stuff.

WPF databinding and converters

I'm trying to databind to a listbox like so:
<ListBox x:Name="MyListBox" Margin="0,0,0,65">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource MyConverter}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The reason I am binding to the whole object and not a property is because my converter will need multiple properties of the object to build the string that it returns.
This works and my string is returned. But then when I change the ObservableCollection that this is based on the value doesn't change on the screen. If I bind to just a single property and change it, then the value does change.
What can I do differently? I can't bind to a single property since I need the entire object in the converter... And the ConverterParameter is already being used.
Remember, if you bind to the "main" property and the value of the main property itself isn't changed, the binding will have no reason to refresh itself. It has no clue that your converter is actually based off of a sub-property. What you can do is use a MultiBinding where you bind not only the "main" property, but also a specific sub-property. This gives your IMultiValueConverter implementation access to the main data object, but because you're also binding to the sub-property that's changing, will also be refreshed when that sub-property's value changes.
You can try using a MultiBinding which I believe updates whenever any of its Bindings are triggered. You can also use an IMultiValueConverter or just take advantage of the StringFormat of the binding.

INotifyPropertyChange does not update converter-based values?

I have an image with source set by a ValueConverter:
<Image Source="{Binding Converter={StaticResource siConv}}" Width="16" Height="16"/>
When the bound object raises a PropertyChanged event (from INotifyPropertyChanged), the image does not change. I assume it is because WPF doesn't know what fields the converter looks at.
How do I tell WPF to rebind/rerun this converter when a certain property changes?
The answer here was to use data triggers, not value converters. The trigger is bound directly to the property, and picks up the propertychanged event. Also, saves writing a bunch of one-time value converters.
And if you really intent on writing a ValueConverter, may be use a MultiValueConverter with all the values that you want the WPF framework to monitor. Any changes to those values, and the framework would update the value it is binded to.
the Path is important:
<Image Source="{Binding Converter={StaticResource siConv}, ConverterParameter=yourproperty, Path=yourproperty, UpdateSourceTrigger=PropertyChanged}" Width="16" Height="16"/>

Resources