I'm going crazy with converters. I know that I must use it to change the "exit value" of my values, when needed, but I don't know how to use right for my case.
I have my simple MVVM (3 fields only) and my main window with a list of my items. The first item is calculated depending on a function, and can show YES or NOT, the other values are binded directly.
This is working well, but I need to change the background and foreground colors depending on the YES or NOT value I have in the first calculated field. For example:
YES (must be blue) - ITEM 1
NO (must be grey) - ITEM 2
YES (must be blue) - ITEM 3
While the internal values in my database are (in this case the calc is modulus):
2 - ITEM 1
3 - ITEM 2
4 - ITEM 3
My ListBox code is this:
<phone:PhoneApplicationPage
x:Class="Pasti.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="My App" Style="{StaticResource PhoneTextNormalStyle}" />
<TextBlock Text="My List" Style="{StaticResource PhoneTextTitle1Style}" />
</StackPanel>
<ListBox x:Name="lstPills" Grid.Row="1" ItemsSource="{Binding AllItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch" Width="440">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Background="HERE MUST GO THE CONVERTER, I SUPOSE">
<TextBlock Text="{Binding IsPair, Mode=TwoWay}"/>
</Border>
<TextBlock
Text="{Binding Name}"
FontSize="{StaticResource PhoneFontSizeLarge}"
Grid.Column="1"
VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</phone:PhoneApplicationPage>
And the CS code is this for this page:
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
// Set the page DataContext property to the ViewModel.
this.DataContext = App.ViewModel;
}
}
For the calculated field, I added this to the Model (_myNumber holds the value I must check):
// Define a custom field based on some database values
// Get is calculated, while set will force it to refresh by Notifying
public string IsPair
{
get
{
return _myNumber % 2 == 0 ? "YES" : "NO";
}
set
{
NotifyPropertyChanged("IsPair");
}
}
NOTE: Because I don't know other way to force the list to refresh, I put the set property to only notify and the TwoWay Mode, and I just do a IsPair = "" when I want it to recalculate. If there are other way to do it, will be welcome.
So, with this info, how can I made a Converter that, based on my IsPair value, set the Background property of the Border to Blue or Grey? I saw a lot of Converter examples, but still don't get the point to do exactly this.
I suppose I must put something like this in the MainPage.cs, under the MainPage Class:
// Converter for the YES-NO column on the list
public class IsPairConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (MY_CALCULATED_VALUE == "YES")
return "Blue";
return "Grey";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
But how to get the MY_CALCULATED_VALUE, and how to set the converter in the Background value of the Border?
So close!
First, bind the background to IsPair and use the converter:
<Border Background="{Binding IsPair, Converter={StaticResource IsPairConverter}}">
<TextBlock Text="{Binding IsPair, Mode=TwoWay}"/>
</Border>
In your converter, create a brush depending on the value:
public class IsPairConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// You might want to add additional checks for type safety
var calculatedValue = (string)value;
var color = calculatedValue == "YES" ? Colors.Blue : Colors.Gray;
return new SolidColorBrush { Color = color };
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And you're done.
If you want the value to be computed only one time, instead of every time IsPair is called, you can do the computation in the setter of MyNumber and assign it to IsPair:
private int myNumber;
public string IsPair { get; protected set; }
protected int MyNumber
{
get
{
return this.myNumber;
}
set
{
this.myNumber = value;
this.IsPair = value % 2 == 0 ? "YES" : "NO";
this.NotifyPropertyChanged("IsPair");
}
}
Related
I'm trying to bind a custom class to a group of 3 Radiobuttons in my WPF app. There should be three possibilities of the class being returned, depending on which button of the group is selected. So i.e.
public class RadioButtonResult
{
public bool Istrue {get; set;}
public string WhichOne {get; set;}
}
should be bound to the 3 radiobuttons in the sense that Button 1 returns
new RadioButtonResult { Istrue = false, WhichOne = "First"}
second one returns an Instance with Istrue = true, etc... I need this because there are 3 possible situations and the element has to bind to both a boolean property and a string property.
I tried using a converter
public class RadioButtonConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
switch (parameter)
{
case "First":
return new RadioButtonResult(false, "First");
case "Second":
return new RadioButtonResult(true, "Second");
case "Third":
return new RadioButtonResult(true, "First");
default:
return new RadioButtonResult(false, "None")
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{ return null; }
}
The radiobuttons themselves should have no text, so I'm not quite sure how to pass the converter parameter to even try this. (I didn't try the ConvertBack yet as I couldn't get the Convert to work)
<RadioButton GroupName="Group1" IsChecked="{Binding TestStatus, Converter=RadioButtonConverter, ConverterParameter="First"}"/>
I tried something like this, but it won't accept text as the parameter. How could I make this converter work?
I don't know how to bind both a string and a bool to a group of 3 Radiobuttons
You can have 6 properties in view model or reuse RadioButtonClass class (consider to implement INotifyPropertyChanged if you want to change values dynamically)
public class ViewModel
{
public RadioButtonResult Button1 { get; } = new RadioButtonResult(false, "First");
... // more buttons
}
<RadioButton GroupName="Group1"
IsChecked="{Binding Button1.Istrue}"
Content="{Binding Button1.WhichOne}" />
public MainWindow()
{
InitializeComponents();
DataContext = new ViewModel();
}
Rather than having a separate boolean property for each radio button, I'd suggest having a single enum value for each radio button group.
To do the binding, you would then need a converter between the enum value and the boolean IsChecked property of the radio button.
public class perValueEqualsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return parameter != null && parameter.Equals(value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value != null && value.Equals(true) ? parameter : Binding.DoNothing;
}
}
The enum property is defined in the ViewModel ...
public enum MyEnum
{
Value1,
Value2,
Value3
}
public class MainViewModel : ViewModelBase
{
private MyEnum _e = MyEnum.Value2;
public MyEnum E
{
get => _e;
set => Set(nameof(E), ref _e, value);
}
}
... and then used as the binding source in the View, via a converter instance
<Window
...>
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Window.Resources>
<conv:perValueEqualsConverter x:Key="ValueEqualsConverter" />
</Window.Resources>
<Grid Margin="24">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="16" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0"
Width=200>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="8" />
<RowDefinition Height="Auto" />
<RowDefinition Height="8" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<RadioButton
Grid.Row="0"
Content="Value 1"
IsChecked="{Binding E, Converter={StaticResource ValueEqualsConverter}, ConverterParameter={x:Static local:MyEnum.Value1}}" />
<RadioButton
Grid.Row="2"
Content="Value 2"
IsChecked="{Binding E, Converter={StaticResource ValueEqualsConverter}, ConverterParameter={x:Static local:MyEnum.Value2}}" />
<RadioButton
Grid.Row="4"
Content="Value 3"
IsChecked="{Binding E, Converter={StaticResource ValueEqualsConverter}, ConverterParameter={x:Static local:MyEnum.Value3}}" />
</Grid>
<Border
Grid.Row="0"
Margin="-8"
BorderBrush="Black"
BorderThickness="2"
CornerRadius="8" />
<TextBlock
Grid.Row="2"
Text="{Binding E}" />
</Grid>
</Window>
I think your viewmodel structure should probably be more like:
public class RadioButtonResult
{
public bool Istrue {get; set;}
public string WhichOne {get; set;}
public List<OptionVM> Options {get; set;}
}
OptionVM would have two properties:
public bool Istrue {get; set;}
public string Descriptor {get; set;}
The setter of IsTrue should initiate some logic.
Hence:
private bool isTrue = false;
public bool Istrue
{ get => isTrue;
set { isTrue = value;
SetTheParentValue();
}
}
SetTheParentValue should be an Action which you inject to the viewmodel. This takes a reference to RadioButtonResult and makes istrue there true or false and sets WhichOne. So you also want a public Action SetTheParentValue on that.
And you should implement inotifypropertychanged.
No converter.
And your logic goes in the action so the classes are re-usable for other groups of radiobuttons.
We don't know enough about your overall structure to give advice on other aspects.
However.
Sets of radiobuttons are a repeating group and that's handled in wpf by binding the itemssource of an itemscontrol to a list or observablecollection of viewmodels. Here that would be of OptionVM. The data is then data templated out into radio buttons.
The ischecked property of the radio button would be bound to IsTrue and the content to Descriptor.
I have a 'GameControl : FrameworkElement'. I have it in the xaml like this:
<local:GameControl x:Name="control"/>
This GameControl has a property that is an own class:
public Gem selectedGem {get; set;}
Now, I want to write this Gem's information into a TextBlock, so that the player will see its properties.
How do I bind my own FrameworkElement's properties to the MainWindow's elements?
--
Full xaml:
<Window x:Class="GemTowerDefense.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:GemTowerDefense"
mc:Ignorable="d"
Title="Gem Tower Defense" Height="670" Width="800"
ResizeMode="NoResize">
<Grid>
<Border Background="Gray" Height="600" Width="600" Margin="3,26,189,3">
<local:GameControl x:Name="control"/>
</Border>
<Border Background="LightSlateGray" HorizontalAlignment="Left" VerticalAlignment="Top" Height="285" Margin="608,181,0,0" Width="170">
<TextBlock x:Name="tbInfo" Text="Gem information">
</TextBlock>
</Border>
</Grid>
</Window>
(Instead of Text=Gem Information, I want the binding to the control's selectedGem, or to one of its string type property)
You make your property a dependency property and when binding that property to the TextBlock.Text, use a converter. Search Stackoverflow to find billion examples on both topics. The binding would look something like this:
Text="{Binding ElementName=control, Mode=OneWay, Path=selectedGem, Converter={local:ExampleConverter}}"
I find it easiest to create converter in code-behind:
public class ExampleConverter : MarkupExtension, IValueConverter
{
public ExampleConverter()
{
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(value != null && value is Gem)
return (value as Gem).GemAsText();
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
#endregion
}
I am writing a program to display text and image associated with the text. The nature of data is such that I may or may not have image for every text. This is information is in collection of objects. Each object has text and path of image. If image is not there then path is empty. The class for the object is
public class MyInfo
{
public DateTime EntryDate { set; get; }
public string NoteText { set; get; }
public string ImagePath { set; get; }
}
I used DataGrid to display the information. 1st column shows text and the second shows image. If there is no image then the 2nd column is empty. This does not look nice and customer is asking to change the UI so that it should take the full row if there is no image. Also he wants to have clear separator between the rows. I already have alternating colors but does not go well with the both text and image in place.
Please suggest how to enhance the grid. If DataGrid is not the right control then what is the other control/approach to resolve it.
Thanks
Personally I would a ListBox and use the ListBox.ItemTemplate to define how the row will look. This will give greater flexibility and better achieve what you want. As Ashok said you will want to use a value converter to convert a empty string into a "Collapsed" visibility option.
Converter example:
public class EmptyStringToCollapsedConverter : IValueConverter
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var s = (value as string);
return String.IsNullOrWhiteSpace(s)? Visibility.Collapsed : Visibility.Visible;
}
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Instead of using DataGrid, I suggest to use the ListBox with a DataTemplate, in a way similar to this:
<ListBox ItemsSource="{Binding Path=MyInfoCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=EntryDate}" />
<TextBlock Grid.Column="1" Text="{Binding Path=NoteText}" />
<Image Grid.Column="2" Source="{Binding Path=ImagePath}" />
</Grid>
</DataTemplate>
</ListBox .ItemTemplate>
</ListBox >
Where MyInfoCollection is an ObservableCollection of MyInfo objects.
Cannot assign a null value via a TextBox Binding to Int32?.
If the TextBox is empty then Int32Null Set is not called.
A red border is around the TexBox indicating a validation exception.
This just does not make sense as Int32? is nullable. If the user removes the integer value from the TextBox I want the Set called so the property is assigned to null.
When it starts int32Null = null and the TextBox is not red.
I tried implementing Validation and set validation = true if the TextBox is empty. But Set is still not called and TextBox is red indicating a validation error.
It seems like I should be able to assign a null value to a nullable via binding.
<Window x:Class="AssignNull.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource self}}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" Text="{Binding Path=Int32Null, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
<TextBox Grid.Row="2" Grid.Column="0" Text="{Binding Path=StringNull, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
</Grid>
</Window>
public partial class MainWindow : Window
{
private Int32? int32Null = null;
private string stringNull = "stringNull";
public MainWindow()
{
InitializeComponent();
}
public Int32? Int32Null
{
get { return int32Null; }
set { int32Null = value; }
}
public string StringNull
{
get { return stringNull; }
set { stringNull = value; }
}
}
Set StringNull does get called and value passed is not null but rather string.empty.
Since Set is not called on Int32Null I don't know what is getting passed.
It was also passing a string.empty to Int32?. Had to convert an empty string to null.
[ValueConversion(typeof(Int32?), typeof(String))]
public class Int32nullConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Int32? int32null = (Int32?)value;
return int32null.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string strValue = value as string;
if(string.IsNullOrEmpty(strValue.Trim())) return null;
Int32 int32;
if (Int32.TryParse(strValue, out int32))
{
return int32;
}
return DependencyProperty.UnsetValue;
}
}
You make a false assumption on how the type converters ought to handle this. So if they do not do what you want, namely turn an empty string into null you'll either have to write your own or use a Binding.Converter that does the conversion for you.
I bind combobox (that is a part if listbox item template) to enum, the selected item is bound to the collection that is bound to listbox.
I use a converter for some logic.
The problem is that the ConvertBack is not invoked on startup, but only when I re-select the item in combobox.
I need it to invoke also on start.
public enum FullEnum
{
Apple,
Banana,
Pear
}
<Window.Resources>
<local:EnumConverter x:Key="enumConverter"/>
<ObjectDataProvider x:Key="DataT"
MethodName="GetValues"
ObjectType="{x:Type sys:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:FullEnum" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="190*" />
<RowDefinition Height="71*" />
</Grid.RowDefinitions>
<ListBox Name="list1" Margin="0,0,0,37">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Label}"></TextBlock>
<ComboBox Height="23" Width="90"
ItemsSource="{Binding Source={StaticResource DataT}}"
SelectedValue="{Binding Path=Oped, Converter={StaticResource enumConverter}}">
</ComboBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
List<Item1> list = new List<Item1>();
public Window1()
{
InitializeComponent();
list.Add(new Item1 { Label="label1" });
list.Add(new Item1 { Label = "label2" });
list.Add(new Item1 { Label = "label3" });
list1.ItemsSource = list;
}
public class Item1
{
public FullEnum Oped { get; set; }
public string Label { get; set; }
}
public class EnumConverterr : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//some code
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((int)value != 0)
return (EnumSuperior)value;
return (EnumSuperior)7;
}
}
The return converter is not called by WPF on initialization because it has just gotten the initial values from the data context. The source and target of the data binding should have the same values so there is no reason to update the source.
You have not posted your convert back logic, but you must have some "state-ful" logic in the converter. Converters should be stateless (no side-effects, immutable). All of the conversion should be based on the value, a parameter, and converter properties that are not modified during the conversion.
If your converter is stateless, all you need to do is initialize the data source properly and you should no longer need that initial convert back call.