Does StringFormat in a TextBox make any sense? - c#

If I have a TextBox like this:
<TextBox Text="{Binding Voltage, StringFormat={}{0} kV}" />
and the property Voltage is e.g. 50, I get "50 kV" in my TextBox. This is what I intended.
But now, if the user wants to enter a new value say 40 and types "40 kV" he gets a red border, because there is a FormatException converting back the value.
System.Windows.Data Error: 7 : ConvertBack cannot convert value '40 kV' (type 'String'). BindingExpression:Path=Voltage; DataItem='VMO_VoltageDefinition' (HashCode=19837180); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String') FormatException:'System.FormatException: Die Eingabezeichenfolge hat das falsche Format.
I don't think that the users of my program will accept this.
So am I doing something wrong or is this feature just not usable in a reasonable way with a TextBox?

I suggest usage of converter:
<TextBox Text="{Binding Voltage, Converter={StaticResource VoltageToString}}" />
Where:
<Window.Resources>
<mstf:VoltageToStringx:Key="VoltageToString" />
</Window.Resources>
And codebehind:
public class VoltageToString: IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return ((int)value).ToString() + " kV";
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return int.Parse((string).Replace(" kV",""));
}
}
That is just an basic example, but you should get the idea how to make it more sophisticated.

StringFormat is property of Binding markup extension and theoretically can be used in any binding. However, mostly it makes sense with one-way bindings only.
You are right, that stringformat does not makes much sense in TextBox.
You can workaround it by converter as David suggested, but I recommend you to display the unit in TextBlock outside TextBox:
<DockPanel>
<TextBlock Text="kV" DockPanel.Dock="Right />
<TextBox Text="{Binding Voltage}" />
</DockPanel>
This feels more natural and gives much better user experience.
alternatively, you can create custom control derived from TextBox with new property called Unit or Description, or whatever and modify the control templeate so it will show the unit. Then the final markup could look like this:
<my:TextBox Text="{Binding Voltage}" Description="kV" />

Related

ComboBox Text ValueConverter Lag - C# WPF XAML

I currently have a combobox in my WPF application, but it seems like there's some lag before the value is converted.
The value converter converts a UUID/GUID to a name, but what shows up on the combobox is first the UUID/GUID for a split second and then the name shows up.
Here's a boiled down version of my combobox:
<ComboBox x:Name="MyComboBox"
Text="{Binding Path=GUID, Converter={StaticResource GUIDToNameValueConverter}, Mode=OneWay}"
ItemTemplate="{StaticResource MyTemplate}"
Style="{StaticResource MyStyle}"
ItemsSource="{Binding Source={x:Static myNameSpace:MyItems}, Path=Items}"
SelectionChanged="MyChangedEventHandler">
</ComboBox>
Here's the general gist of my valueconverter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string GUID = (string)value;
MyObject myObject = GetObjectById(GUID);
return myObject.name;
}
Essentially, I want to get rid of the split second display of the GUID before the actual name shows up. I'm unsure why the GUID event shows up since the converter takes care of converting that to a name.
Let me know if this is enough information to have this question answered, if not, please request more parts of my code!

How to implement a converter from string to Visibility

So I have a ProgressRing and a TextBlock and I am trying to implement this basic hack, which is to display both elements when TextBlock's Text gets assigned a value (anything other than null), else both elements should hide when TextBlock's Text is null.
My Xaml looks like below. TextBlock's Text is binded to MessageForProgressRing and its Visibility is binded to both MessageForProgressRing and TargetNullValue. Same for me ProgressRing:
<StackPanel Panel.ZIndex="100" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center">
<mahControls:ProgressRing Height="50" IsActive="True" Width="50" Visibility="{Binding MessageForProgressRing, TargetNullValue=Collapsed, FallbackValue=Visible}" Foreground="White" Margin="0,0,0.2,0" />
<TextBlock Text="{Binding MessageForProgressRing}" Visibility="{Binding MessageForProgressRing, TargetNullValue=Collapsed, FallbackValue=Visible}"/>
</StackPanel>
Then, in code behind I just trigger the property and assign it a value on some button event handlers:
private void closeApplicationButtonTask()
{
((CaptureViewModel)DataContext).MessageForProgressRing = "Closing... ";
Application.Current.MainWindow.Close();
}
However, in my ViewModelBase (the parent of all my view models) it pops an error on OnPropertyChanged saying:
Requested value 'Closing...' was not found.
I think I need a converter because Visibility is binded to Closing... right? If yes how can I achieve it?
P.S I couldn't do it in OnPropertyChanged because I don't see the value to assign it. Also I don't think it is a good idea since it gets called big time before, during and after the execution.
I usually prefer to solve this problem by having a boolean property in my view model (e.g. HasMessageForProgressRing or IsProgressRingVisible). It's usually a more general-purpose solution. Then you can use a BooleanToVisibilityConverter.
If you truly want to implement a converter, just create a class that implements IValueConverter. The Convert implementation of this should be a piece of cake for your simple use case. ConvertBack isn't necessary in most cases (and won't be in yours). It would look something like this:
public class NullToCollapsed : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value != null ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

How to find out which button is selected?

<GroupBox x:Name="groupBox" Header="Operating System" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="74" Width="280">
<StackPanel>
<RadioButton GroupName="Os" Content="Windows 7 (64-bit)" IsChecked="True"/>
<RadioButton GroupName="Os" Content="Windows 7 (32-bit)" />
</StackPanel>
</GroupBox>
I have several radio button groups in my application
How can I access which one has been checked in the Code-Behind using C#?
Is it absolutely necessary to use x:Name= on each RadioButton or is there a better way?
Code samples always appreciated
Yes! There is a better way, its called binding. Outside of binding, you are pretty much stuck (I can imagine handling all the checked events separately, and assigning to an enum, but is that really better?)
For radio buttons, you would typically use an enum to represent all the possible values:
public enum OsTypes
{
Windows7_32,
Windows7_64
}
And then bind each of your radio buttons to a global "selected" property on your VM. You need a ValueEqualsConverter for this:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.Equals(parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((bool)value) ? parameter : Binding.DoNothing;
}
And then your radio buttons look like:
<RadioButton Content="Windows 7 32-bit"
IsChecked= "{Binding CurrentOs,
Converter={StaticResource ValueEqualsConverter},
ConverterParameter={x:Static local:OsTypes.Windows7_32}}"
Of course, you have a property in your VM:
public OsTypes CurrentOs {get; set;}
No x:Name, complicated switch statements, or anything else. Nice, clean, and well designed. MVVM works with WPF, use it!

Can you reference properties within other properties in XAML?

So I have a ScrollBar whose Maximum property binds to a dependency object, and I want the LargeChange and SmallChange properties to always be constant fractions of this length. I currently implement this in the code-behind but am trying to switch to an all-XAML approach.
So instead of this:
curSpeedScrollbar.SetBinding(Slider.MaximumProperty, speedBinding);
curSpeedScrollbar.LargeChange = curSpeedScrollbar.Maximum / 1000;
curSpeedScrollbar.SmallChange = curSpeedScrollbar.Maximum / 10000;
curSpeedScrollbar.ViewportSize = curSpeedScrollbar.Maximum / 16;
I'm shooting for something like this (but with the correct syntax):
<ScrollBar BorderThickness="1" Height="25" HorizontalAlignment="Stretch" LargeChange = "Maximum / 1000" Margin="208,62,130,106" Maximum="{Binding MaxValue}" Name="curSpeedScrollbar" Orientation="Horizontal" SmallChange = "Maximum / 10000" VerticalAlignment="Stretch" ViewportSize="Maximum / 16" Width="431" Grid.Column="2" Grid.Row="1" />
Can anyone show me the proper syntax to do this? Or if there is a better way to achieve what I want (no/minimal code-behind, all/mostly in XAML)? Thanks!
There are two parts to your question, so I will respond to each of them individually:
How to bind to a property from within a property
This is possible, and in fact, it is constantly done - bindings are (almost) always added from a property to refer to another property. Normally, you just do not refer to another property of the same object, but this can be done with RelativeSourceMode.Self:
LargeChange="{Binding Maximum, RelativeSource={RelativeSource Self}}"
How to bind to a multiplied value of a property
For this, you will have to create a value converter. A very static version of such a converter could look like this:
public class DividingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((double)value) / 1000;
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
You may have to check value for DependencyProperty.UnsetValue, as sometimes, dependency properties are unset at some point.
For a more dynamic version, you can use the ConverterParameter property from bindings to supply the value to divide by.
This converter can then be added as a static resource in your XAML:
<somewhere:DividingConverter x:Key="divConv"/>
(where somewhere is a namespace prefix for your converter namespace)
It can then be used in bindings such as the above one:
LargeChange="{Binding Maximum, RelativeSource={RelativeSource Self}, Converter={StaticResource divConv}}"
You can use a converter to achieve this behavior.
A math converter for these operations can be found here:
http://www.codeproject.com/Articles/239251/MathConverter-How-to-Do-Math-in-XAML
To just bind some property to the value of another property of the same control, you can use RelativeSource.Self:
<ScrollBar Maximum="{Binding MaxValue}" SmallChange="{Binding RelativeSource={RelativeSource Self}, Path=Maximum}" ... />
This would set SmallChange to the same value as Maximum.
However, this is as far as you get, since calculations are not possible directly within XAML. For this you'd need a converter that, for example, takes the Maximum property's value as input and returns the calculation's result.

Bind textblock to dictionary value for key in XAML?

I have a model with an enum property (in this case, related to Export Control Regulations). When displaying the value to the user, I want to show a corresponding string. Sometimes this is in ComboBox (where the user can select a value), and sometimes it is in a TextBlock (where it is read-only).
Example: for ExportRegulationType.EAR, I want to display "EAR", while for ExportRegulationType.DoNotExport, I want to display "Do Not Export". Note that I don't have any language localization needs, but I recognize the issue...
Currently, in my ViewModel, I have a property that returns a string based on the current enum value, and also another property that returns a Dictionary<ExportRegulationType, string>. For the ComboBoxes, I can bind ItemsSource to the dictionary property, and for the TextBlocks, I can bind to the string property. This works, but is kind of clumsy.
Two questions:
1) It seems to me that I should be able to declare the dictionary (with keys and values) as a static resource in XAML (probably in App.xaml), and use that for the ItemsSource for the ComboBox version. However, I can't figure out how to declare and reference such a thing. How can I do that?
2) Assuming the above is in place, I would think I could also set up a binding with the textblock, so based on the enum property, it will look up the string in the dictionary.
I have seen the following questions relating to a static or dynamic enum value. The first isn't adequate, and the second isn't answered...
These should be a XAML-only, and will enable me to remove the methods from my ViewModel (having only the one exposed ExportRegulationType enumerated property. Are these possible?
Edit: Additional information:
In the application, I will have many different sets of views, models, and ViewModels. However, as export control regulations are a common and consistent requirement, I am using composition to keep it DRY. i.e., Models A and B both have an ExportControl model. ViewModels A1, A2, B1 and B2 will have an ExportControlViewModel. The views will have controls bound to the ExportControlViewModel of their ViewModel. The views will have either a ComboBox or a TextBlock, but not both (Depending on if the user can change the value).
I don't know if this will work for your case, but here is a possible solution. In your view model, expose a ExportRegulationType property and then create a value converter to display your desired string.
First create your value converter:
class ExportRegulationTypeToStringConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ExportRegulationType regType = (ExportRegulationType)value;
switch(regType)
{
case ExportRegulationType.EAR:
return "EAR";
case ExportRegulationType.DoNotExport:
return "Do Not Export";
//handle other cases
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Binding.DoNothing;
}
#endregion
}
Then add a reference to your converter in your xaml. local is the namespace in which your class is located.
<local:ExportRegulationTypeToStringConverter x:Key="exportRegConverter" />
Finally, set the value of your text box to use the converter. pathToEnum is the property exposed on your ViewModel of type ExportRegulationType.
<TextBlock Text="{Binding pathToEnum, Converter={StaticResource exportRegConverter}}" />
Use ObjectDataProvider to fill the ComboBox with the values of the enum.
<Window.Resources>
<ObjectDataProvider x:Key="dataFromEnum"
MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:ExportRegulationType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
Now we create the ComboBox and use a container style with our value converter to display the desired strings for our enum.
<ComboBox ItemsSource="{Binding Source={StaticResource dataFromEnum}}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Content" Value="{Binding Converter={StaticResource exportRegConverter}}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
Instead of the Dictionary you have another option.
See the following question: WPF Binding a ListBox to an enum, displaying the Description Attribute
You could add a Description Attribute to your enums like this
public enum ExportRegulationType
{
[Description("EAR")]
EAR,
[Description("Do Not Export")]
DoNotExport
}
And when you want to display it, you can just use EnumDescriptionConverter Converter found in the question I linked
I solved this with a blend of what #Dylan and #Meleak wrote. I'm putting this as an answer to show what the final solution was:
First, I implemented an IValueConverter, (based on #Meleak's answer):
class EnumDescriptionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Enum regulation = (Enum)value;
return GetEnumDescription(regulation);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return String.Empty;
}
/// <summary>
/// Returns text intended for display based on the Description Attribute of the enumeration value.
/// If no Description Attribute is applied, the value is converted to a string and returned.
/// </summary>
/// <param name="enumObj">The enumeration value to be converted.</param>
/// <returns>Text of the Description Attribute or the Enumeration itself converted to string.</returns>
private string GetEnumDescription(Enum enumObj)
{
// Get the DescriptionAttribute of the enum value.
FieldInfo fieldInfo = enumObj.GetType().GetField(enumObj.ToString());
object[] attributeArray = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributeArray.Length == 0)
{
// If no Description Attribute was found, default to enum value conversion.
return enumObj.ToString();
}
else
{
// Get the text of the Description Attribute
DescriptionAttribute attrib = attributeArray[0] as DescriptionAttribute;
return attrib.Description;
}
}
}
I tagged my enum (note that several values are not tagged as the desired text is the same as the value itself):
public enum ExportRegulationType
{
[Description("Not Determined")]
NotDetermined, // Export authority not determined
EAR, // Controlled by EAR Regulations
ITAR, // Controlled by ITAR Regulations
[Description("Do Not Export")]
DoNotExport, // Export not allowed
Unrestricted // Export not controlled
}
In my App.xaml, I declared the ObjectDataProvider to get the list of enum values and the EnumDisplayConverter (Here since they will be used by several different views):
<Application.Resources>
[Other stuff...]
<ObjectDataProvider MethodName="GetValues"
ObjectType="{x:Type sys:Enum}"
x:Key="ExportRegulationValues">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="models:ExportRegulationType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<local:EnumDescriptionConverter x:Key="ExportDisplayConverter"/>
</Application.Resources>
For a TextBlock:
<TextBlock Text="{Binding Export.Regulation, Converter={StaticResource ExportDisplayConverter}}"/>
For a Combo Box:
<ComboBox ItemsSource="{Binding Source={StaticResource ExportRegulationValues}}"
SelectedValue="{Binding Document.Export.Regulation}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource ExportDisplayConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This works perfectly!
Use an ObjectDataProvider
then bind the ComboBox's Items to it, and set "DisplayMemberPath" to "Value".
What this should do is to show the values of your dictionary, but in code-behind the SelectedValue is a KeyValuePair<>.
For your textblock, use a Binding using ElementName=yourcombobox and Path=SelectedItem:
<TextBlock Text="{Binding SelectedItem, ElementName=yourcombobox}" />
Let me know how it goes =)
Here is a blog post of mine with an approach using attached behaviors.
It is based on the principle that different enumeration values don't need to be limited to switching strings. Instead, you can declare whatever pieces of UI you want to represent each value (strings, images, different controls and layouts, etc.) and use an attached behavior to control their visibility.
Your situation, then, can be framed as having two different text blocks, each bound to the same property of type ExportRegulationType. Since they are bound to the same property, their visibilities are mutually exclusive:
<Grid>
<TextBlock
Text="EAR"
local:EnumVisibility.Value="{Binding ExportRegulationType}"
local:EnumVisibility.TargetValue="EAR"
/>
<TextBlock
Text="Do Not Export"
local:EnumVisibility.Value="{Binding ExportRegulationType}"
local:EnumVisibility.TargetValue="DoNotExport"
FontWeight="Bold"
/>
</Grid>
I included the FontWeight="Bold" to show that you can make different decisions for each enumeration value. This also supports XAML localization because the text is set like any other text block.
See the post for a complete walkthrough of the solution, code samples, and a zip file containing the framework and an example application.
Edit in response to additional information:
Here is another post in the same series which describes how to select enumeration values with Selector controls.
A ComboBox bound to the ExportRegulationType property would look this this:
<ComboBox local:EnumSelector.SelectedValue="{Binding ExportRegulationType, Mode=TwoWay}">
<ComboBoxItem Content="EAR" local:EnumSelector.ItemValue="EAR" />
<ComboBoxItem Content="Do Not Export" local:EnumSelector.ItemValue="DoNotExport" />
</ComboBox>
We associate each item with an enumeration value, then use a TwoWay binding to EnumSelector.SelectedValue so it will write back to the view model's property whenever it changes.
This provides the same flexibility as with the text blocks: you can make whatever decisions you want about how to set the text and what is contained by each item.

Categories

Resources