I have a GridView that contains a list of files, created dates, and file sizes. Below the grid I have a textblock that says "X Files Selected. Y MB". I can bind to SelectedItems.Count just fine, but can I easily bind to the sum of the file sizes for those that are selected?
The question marks below should be the sum of the SelectedItems fileSize column values. Any ideas?
<TextBlock HorizontalAlignment="Right">
<TextBlock.Text>
<MultiBinding StringFormat=" {0} Files Selected. {1} MB">
<Binding ElementName="FilesList" Path="SelectedItems.Count"></Binding>
<Binding ElementName="FilesList" Path="SelectedItems.?????"></Binding>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
I know I can get this done in the codebehind - but I'd like to keep my codebehind empty and do it in the XAML. This is the codebehind code:
private void FilesList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
double x = 0;
foreach (FileInfo fileInfo in FilesList.SelectedItems)
{
x += fileInfo.Length;
}
}
You're going to have to use a converter for this. An example:
Xaml:
<MultiBinding StringFormat=" {0} Files Selected. {1} MB">
<Binding ElementName="FilesList" Path="SelectedItems.Count"></Binding>
<Binding ElementName="FilesList" Path="SelectedItems" Converter="{StaticResource sumconverter}"></Binding>
</MultiBinding>
Codebehind:
[ValueConversion(typeof(ListViewItem[]), typeof(string))]
class SumConverter : IValueConverter {
public object Convert( object value, Type targetType, object parameter, CultureInfo culture ) {
int size = 0;
ListViewItem[] items = (ListViewItem[])value;
if(items != null){
foreach(var lvi in items){
Someclass sc = lvi.content as Someclass;
if(sc!=null){
size += sc.Size;
}
}
}
return (size / 1000) + "MB";
}
public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture ) {
return null;
}
}
Sadly, you will not be able to do this in XAML, alone.
You will need to bind to the SelectedItems themselves and provide a value converter. The value converter needs to iterate over each file path, create a FileInfo object from the path, and sum up the sizes using the FileInfo.Length property.
You have 3 options.
You can create a sum property in whatever entity you are binding to (your FilesList entity). This will require you to change your FilesList collection to a CollectionView so you can get access to the SelectedItems property from your ViewModel (if you aren't doing this already).
I've never tried this, but you might be able to use Kent Boogaart's "Expression Value Converter" that allows you to write small bits of C#-Like code in your binding expressions: http://wpfconverters.codeplex.com/
Provide a simple ValueConverter that converts a collection of whatever your entity is to a decimal or whatever (this is probably the simplest thing to do).
Related
I need to create a Converter that takes some value and returns another. This will be used to set the textblock RotateTransform.Angle value in XAML.
If I hard-code the value to a static number the textblock gets rotated successfully. But when it goes through the converter it does not get rotated.
Any insight would be appreciated.
Thanks.
Hard-coded value (works):
<TextBlock ...
<RotateTransform CenterX="0.5" CenterY="0.5">
<RotateTransform.Angle>
10
</RotateTransform.Angle>
</RotateTransform>
Going through a Converter (does not work):
<TextBlock ...
<RotateTransform CenterX="0.5" CenterY="0.5">
<RotateTransform.Angle>
<MultiBinding Converter="{StaticResource RelativeToAbsoluteRotationConverter}">
<Binding Path="RelativeAngle" />
</MultiBinding>
</RotateTransform.Angle>
</RotateTransform>
Converter class:
public class RelativeToAbsoluteRotationConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// Add logic here... Function is hit, but no rotation ever takes place.
return 10; // irrelevant
}
// ...
Output window:
System.Windows.Data Error: 5 : Value produced by BindingExpression is not valid for target property. Int32:'30' MultiBindingExpression:target element is 'RotateTransform' (HashCode=57454947); target property is 'Angle' (type 'Double')
The solution is to modify the object Convert() method and instead of returning an int (e.g. 10), we return a double value (e.g. 10d or 10.0).
return 10d; // This works
I have BudgetControlType Properties that has 1 .. 7 value
if(BudgetControlType ==1)
dataComboBox1.Visibility=Visibility.Visiblile;
dataComboBox2 to dataComboBox7 =Visibility.Hidden;
if(BudgetControlType ==2)
dataComboBox1.Visibility=Visibility.Visiblile;
dataComboBox2.Visibility=Visibility.Visiblile;
dataComboBox3 to dataComboBox7 =Visibility.Hidden;
and so on...
How to do this in xaml?
Here is another approach I have used in the past using WPFConverters.
<TabItem.Visibility>
<Binding Path="SomeObservableCollection.Count">
<Binding.Converter>
<converters:ConverterGroup>
<converters:ExpressionConverter Expression="{}{0} > 0" />
<BooleanToVisibilityConverter />
</converters:ConverterGroup>
</Binding.Converter>
</Binding>
</TabItem.Visibility>
The ConvertGroup allows for multiple converters to be run sequentially.
The ExpressionConverter lets you define an arbitrary expression. In my case I want the TabItem to be visible if the collection count is greater than zero. Being defined in xaml means escaping characters and a somewhat awkward syntax but it works well enough!
The BooleanToVisibilityConverter converts the boolean result from the expression to our desired visibility.
For Elham, BudgetControlType could be bound to as long as it implemented INotifyPropertyChanged. An equals expression is done like this (I'm returning true if the bound value equals 7):
<converters:ExpressionConverter Expression="{}{0} == 7" />
You can use 1,2,4,8,... and convert it to Visibility
for example if your int number is 6 (2+4) then Control with paramerter 2 and Control with parameter 4 is Visible!
public class IntToVisibilityConverter:IValueConverter
{
private int val;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int intParam = (int)parameter;
val = (int)value;
return ((intParam & val) != 0) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
And in xaml :
<ComboBox Visibility="{Binding Path=MyEnum,UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IntToVisibilityConverter}, ConverterParameter=1}"/>
<ComboBox Visibility="{Binding Path=MyEnum,UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IntToVisibilityConverter}, ConverterParameter=2}"/>
<ComboBox Visibility="{Binding Path=MyEnum,UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IntToVisibilityConverter}, ConverterParameter=4}"/>
The best way I'd say would be to go with properties on your ViewModel, and bind to them.
example (you'll have to massage it a bit, but it's fairly simple from here) :
public Visibility dtcb1 { get; set; }
// all the rest till 7
// Somewhere in your logit / constructor :
dtcb1 = BudgetControlType == 1 ? Visible : Hidden;
// and so on
And on your xaml you'll bind your visibility to dtcb1
You can make the property boolean, and use a boolean to visibility converter as well (as per this answer for example, or just google yourself)
I need to display a value in a currency format (EUR / USD / YEN etc.) depending on the currency value stored in the database.
In the database the data is stored like:
Id Value Currency
1 1000 EUR
2 1500 USD
3 9650 USD
In XAML, I'd like to know how I can show the value in a correct currency format.
For example, if I read the first line from the database (Id=1), I like to show it on UI as €1,000 but if I read the second line (Id=2) it should be shown as $1,500.
Right now my XAML MVVM binding looks like this:
<TextBlock Text="{Binding SelectedItem, StringFormat=c0}" ...
...and for me this displays the value always as $1,500 which I do not want.
A converter class can do the trick for you to achieve the desired behavior
public class CurrencyConverter : MarkupExtension, IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return GetCurrency(values);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
private string GetCurrency(object[] values)
{
switch (values[1].ToString())
{
case "USD":
return string.Format(new System.Globalization.CultureInfo("en-US"), "{0:C}", values[0]);
case "EUR":
return string.Format(new System.Globalization.CultureInfo("en-GB"), "{0:C}", values[0]);
default:
return string.Format(new System.Globalization.CultureInfo("en-US"), "{0:C}", values[0]);
}
}
}
Simply use the converter in XAML with your TextBlock bindings.
<TextBlock DataContext="{Binding SelectedItem, ElementName=listBox}">
<TextBlock.Text>
<MultiBinding Converter="{local:CurrencyConverter}">
<Binding Path="Value"/>
<Binding Path="Currency"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
The string format you're using is based on current system locale so it's not a way to go at all. In your situation you would be interested in something like such a converter: http://blogs.msdn.com/b/bencon/archive/2006/05/10/594886.aspx
Pass in two values (currency and amount), return back a string representation to be shown on UI.
I saw in this post a solution that fits exactly my needs https://stackoverflow.com/a/8858815/1462911.
But I don't really know how to properly implement it.
I have for now a PositionConverter which converts coordinates in Strings but i'd like to pass through its parameter the ActualWidth of its Parent (a Canvas).
Does my ConverterHelper have to implement IValueConverter and DependencyObject or just DependencyObject?
I'm a bit lost....
What you need is best accomplished through an IMultiValueConverter and a MultiBinding:
public class PositionConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var scale = (double)values[0]; // this is your [0, 1] double
var max = (double)values[1]; // this is the ActualWidth
return scale * max;
}
}
The binding would look like:
<Element.Property>
<MultiBinding Converter="{StaticResource myConverter}">
<Binding Path="path_to_the_original_double" />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorLevel=1}"
Path="ActualWidth" />
</MultiBinding>
</Element.Property>
I'm in the process of writing validation Rules to various controls in XAML. I would like to attach validationRules to controls at runtime and not in XAML. I was thinking of using Converters . but any ideas / thoughts which would be better way to accomplish this.
Sample code:
<TextBox Name="txtFirstName" > <TextBox.Text> <Binding Path="FirstName" ValidatesOnDataErrors="True" PropertyChanged" >
<Binding.ValidationRules>
<Binding Converter="{StaticResource validationConverter}"/>
</Binding.ValidationRules>
</Binding>
public class ValidationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
Binding b = new Binding();
b.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
b.ValidatesOnDataErrors = true;
b.NotifyOnValidationError = true;
b.ValidationRules.Add(new RangeValidationRule { ErrorMessage = "Error shown from Converter ", MinValue = 10, MaxValue = 50 });
return b;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Thanks,
A method that I have used is to create different Styles that have my custom validation built into them. Then it is just a matter of assigning the proper Style at runtime that contains the validation that is needed.
EDIT
Below is a basic example that creates a style called NumericTextBox:
<Style x:Key="NumericTextBox">
<Setter Property="TextBox.VerticalAlignment"
Value="Stretch"/>
<Setter Property="TextBox.VerticalContentAlignment"
Value="Center"/>
<Setter Property="TextBox.Height"
Value="24"/>
<Setter Property="TextBox.Margin"
Value="0,2,0,2"/>
<EventSetter Event="UIElement.PreviewTextInput"
Handler="tbx_DigitCheck"/>
<EventSetter Event="UIElement.PreviewKeyDown"
Handler="tbx_OtherCharCheck"/>
<EventSetter Event="UIElement.PreviewDragEnter"
Handler="tbx_PreviewDragEnter"/>
</Style>
The following method is put in the code-behind file for the resource dictionary where the Style is stored. This method makes sure that only numbers and the valid decimal separator can be entered in the textbox. Each character is checked for vailidity before it is actually displayed in the textbox.
Public Sub tbx_DigitCheck(ByVal sender As Object, _
ByVal e As TextCompositionEventArgs)
//Retireve the sender as a textbox.
Dim tbx As TextBox = CType(sender, TextBox)
Dim val As Short
//Get the current decimal separator.
Dim dSep As String = Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator
//Check to make sure that a decimal separator character has not
//already been entered in the textbox. Only make this check
//for areas of the text that are not selected.
If e.Text = dSep And tbx.SelectedText.Contains(dSep) Then
//Do Nothing. Allow the character to be typed.
ElseIf e.Text = dSep And tbx.Text.Contains(dSep) Then
e.Handled = True
End If
//Only allow the decimal separator and numeric digits.
If Not Int16.TryParse(e.Text, val) And Not e.Text = dSep Then
e.Handled = True
End If
End Sub