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.
Related
We used to develop application with WinForms and nowadays we are trying to migrate it to WPF, starting from zero. In our application we have 3 main parts on screen which are Header (all main menu items), body (based on MDI container, content can be changed) and the footer (where general status is displayed, logo etc.) Whenever a user clicks on different menuitem from header part, the body part would change it's children to that Panel/Form.
There are lot's of good examples/tutorials on the Internet but I am confused about how to achieve to create a navigation service that allows to switch the view of body part.
Any suggestions would be appriciated, thanks in advance.
There are indeed multiple ways to archive this result. I will try and explain the very basic/easiest way to get the result.
While this will not provide example in combination with Menu Control, I think it will help you to understand the concept
In your MainWindow you can split use Grid layout and split the space into 3 parts as you wanted. Your Main window Xaml should look something like this :
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<ContentControl x:Name="Header"/>
<ContentControl x:Name="Content" Grid.Row="1/>
<ContentControl x:Name="Footer" Grid.Row="2"/>
</Grid>
In your content control you can insert your "UserControls" for the Header,Content,Footer. Now to the navigation part:
As mentioned there are many ways to archive this and I will describe what I do consider the easiest way (not the most flexible way however, so keep that in mind).
First I will suggest to make a navigation Model:
public class NavigationModel
{
public NavigationModel(string title, string description, Brush color)
{
Title = title;
Description = description;
Color = color;
}
public string Title { get; set; }
public string Description { get; set; }
public Brush Color { get; set; }
public override bool Equals(object obj)
{
return obj is NavigationModel model &&
Title == model.Title &&
Description == model.Description &&
Color == model.Color;
}
public override int GetHashCode()
{
return HashCode.Combine(Title, Description, Color);
}
}
We create a new class that will handle the navigation collection, lets call it navigation service.
public class NavigationService
{
public List<NavigationModel> NavigationOptions { get=>NavigationNameToUserControl.Keys.ToList(); }
public UserControl NavigateToModel(NavigationModel _navigationModel)
{
if (_navigationModel is null)
//Or throw exception
return null;
if (NavigationNameToUserControl.ContainsKey(_navigationModel))
{
return NavigationNameToUserControl[_navigationModel].Invoke();
}
//Ideally you should throw here Custom Exception
return null;
}
//Usage of the Func, provides each call new initialization of the view
//If you need initialized views, just remove the Func
//-------------------------------------------------------------------
//Readonly is used only for performance reasons
//Of course there is option to add the elements to the collection, if dynamic navigation mutation is needed
private readonly Dictionary<NavigationModel, Func<UserControl>> NavigationNameToUserControl = new Dictionary<NavigationModel, Func<UserControl>>
{
{ new NavigationModel("Navigate To A","This will navigate to the A View",Brushes.Aqua), ()=>{ return new View.ViewA(); } },
{ new NavigationModel("Navigate To B","This will navigate to the B View",Brushes.GreenYellow), ()=>{ return new View.ViewB(); } }
};
#region SingletonThreadSafe
private static readonly object Instancelock = new object();
private static NavigationService instance = null;
public static NavigationService GetInstance
{
get
{
if (instance == null)
{
lock (Instancelock)
{
if (instance == null)
{
instance = new NavigationService();
}
}
}
return instance;
}
}
#endregion
}
This service will provide us with action to receive desired UserControll (note that I am using UserControl instead of pages, since they provide more flexibility).
Not we create additional Converter, which we will bind into the xaml:
public class NavigationConverter : MarkupExtension, IValueConverter
{
private static NavigationConverter _converter = null;
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (_converter is null)
{
_converter = new NavigationConverter();
}
return _converter;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
NavigationModel navigateTo = (NavigationModel)value;
NavigationService navigation = NavigationService.GetInstance;
if (navigateTo is null)
return null;
return navigation.NavigateToModel(navigateTo);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> null;
}
In our MainWindows.xaml add reference to the Converter namespace over xmlns, for example :
xmlns:Converter="clr-namespace:SimpleNavigation.Converter"
and create insance of converter :
<Window.Resources>
<Converter:NavigationConverter x:Key="NavigationConverter"/>
</Window.Resources>
Note that your project name will have different namespace
And set the Add datacontext to the instance of our Navigation Service:
You can do it over MainWindow.Xaml.CS or create a ViewModel if you are using MVVM
MainWindow.Xaml.CS:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = Service.NavigationService.GetInstance.NavigationOptions;
}
}
Now all is left to do is navigate. I do not know how about your UX, so I will just provide example from my github of the MainWindow.xaml. Hope you will manage to make the best of it :
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<ListView
x:Name="NavigationList"
ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<Border
Height="35"
BorderBrush="Gray"
Background="{Binding Color}"
ToolTip="{Binding Description}"
BorderThickness="2">
<TextBlock
VerticalAlignment="Center"
FontWeight="DemiBold"
Margin="10"
Text="{Binding Title}" />
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<ContentControl
Grid.Column="1"
Content="{Binding ElementName=NavigationList,Path=SelectedItem,Converter={StaticResource NavigationConverter}}"/>
</Grid>
Just in case I will leave you a link to github, so it will be easier for you
https://github.com/6demon89/Tutorials/blob/master/SimpleNavigation/MainWindow.xaml
Using same principle to use Menu Navigation
<Window.DataContext>
<VM:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<Converter:NavigationConverter x:Key="NavigationConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Menu>
<MenuItem Header="Navigaiton"
ItemsSource="{Binding NavigationOptions}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem
Command="{Binding DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Header="{Binding Title}"
Background="{Binding Color}"
ToolTip="{Binding Description}">
</MenuItem>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
<ContentControl
Grid.Row="1"
Background="Red"
BorderBrush="Gray"
BorderThickness="2"
Content="{Binding CurrentView,Converter={StaticResource NavigationConverter}}"/>
<Border Grid.Row="2" Background="{Binding CurrentView.Color}">
<TextBlock Text="{Binding CurrentView.Description}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</Grid>
And we have in VM List of the navigation Models, Current Model and the navigation command :
public class MainViewModel:INotifyPropertyChanged
{
public List<NavigationModel> NavigationOptions { get => NavigationService.GetInstance.NavigationOptions; }
private NavigationModel currentView;
public NavigationModel CurrentView
{
get { return currentView; }
set
{
currentView = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("CurrentView"));
}
}
RelayCommand _saveCommand;
public event PropertyChangedEventHandler PropertyChanged;
public ICommand NavigateCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand(Navigate);
}
return _saveCommand;
}
}
private void Navigate(object param)
{
if(param is NavigationModel nav)
{
CurrentView = nav;
}
}
}
Sorry for long reply
I think that you not necessary have to start from scratch. You may have a look:
https://qube7.com/guides/navigation.html
In a Windows Phone 8 app,
I have a listbox with 2 TextBlocks and a button.
I have a list of 2 strings and a boolean & I am able to bind the strings to the TextBlocks.
<ListBox Name="ListboxTest">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key}" TextWrapping="Wrap"/>
<TextBlock Text="{Binding Value}" TextWrapping="Wrap"/>
<Button />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And this is the C# code to bind to the list box.
public class Detail
{
public string Key { get; set; }
public string Value { get; set; }
public bool check { get; set; }
}
public List<Detail> ClassList = new List<Detail>();
ListboxTest.ItemsSource = ClassList;
I want to display the button only when the boolean value is true.
How do I do it?
Take a look at this. Actually what you really need is a Converter by implementing the IValueConverter. This is also a good example where you could read about it. Bind the boolean value with the visibility property of the button and you are done! ;)
You can use boolean to visibility converter to hide, show button
Here are example:
public class BoolToVisibility : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var boolValue = false;
if (value != null) boolValue = (bool)value;
return boolValue ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
In app.xaml
<my:BoolToVisibility x:Key="BoolToVisibility"/>
In your data template
<Button Visibility="{Binding Path=YourBoolProperty,Converter={StaticResource BoolToVisibility}}>
Or, you could add this property to the Detail class:
public Visibility ButtonVisibility {
get {
return this.check == true ? Visibility.Visible : Visibility.Collapsed;
}
}
And then just bind the button's Visibility to the ButtonVisibility property without any converters.
<Button Visibility="{Binding ButtonVisibility}" />
Please try those use Triggers.
Various Triggers in windows phone Msdn.
Please use ObservableCollection in WP8 for binding instead of List.
Please make your properties are implemented with INotifyPropertyChanged If your Boolean property is not implemented with inotifypropertychanged the view will not know the value is changed.hence the Data trigger will not work.
Namespace
xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ec="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
x:Class="XXX_XXXX"
<Button Content="My button"
Stretch="None"
HorizontalAlignment="Stretch"
VerticalAlignment="Top">
<interactivity:Interaction.Triggers>
<ec:DataTrigger Binding="{Binding Check}" Value="True">
<ec:ChangePropertyAction PropertyName="Visibility">
<ec:ChangePropertyAction.Value>
<Visibility>Visible</Visibility>
</ec:ChangePropertyAction.Value>
</ec:ChangePropertyAction>
</ec:DataTrigger>
<ec:DataTrigger Binding="{Binding Check}" Value="False">
<ec:ChangePropertyAction PropertyName="Visibility">
<ec:ChangePropertyAction.Value>
<Visibility>Collapsed</Visibility>
</ec:ChangePropertyAction.Value>
</ec:ChangePropertyAction>
</ec:DataTrigger>
</interactivity:Interaction.Triggers>
</Button>
Note Answered from phone syntax may not be correct
I am trying to bind a combobox with an empty item. For this I am using the CompositeCollection as I have read in many of the questions.
This is working currently. However using a composite collection messes up the grouping that I had. Either I get the blank in the combobox or I get the grouping. I want both be available.
Here is the sample code that I have been playing with:
Xaml
<Window.Resources>
<local:CourseConverter x:Key="CourseConverter" />
<DataTemplate DataType="{x:Type local:Course}">
<TextBlock Text="{Binding Path=CourseName}" />
</DataTemplate>
<CollectionViewSource x:Key="CoursesCVS" Source="{Binding Courses}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Major" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.2*"/>
<RowDefinition Height="Auto "/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="Select Course" Grid.Row="1" Margin="15,5,0,5" Width="200" />
<ComboBox Grid.Row="2" Width="200" Margin="15,5,0,5"
ItemsSource="{Binding Source={StaticResource CoursesCVS}}"
SelectedItem="{Binding SelectedCourse, Converter={StaticResource CourseConverter}}">
<ComboBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ComboBox.GroupStyle>
<!--<ComboBox.ItemsSource>
<CompositeCollection>
<ComboBoxItem Content="" />
<CollectionContainer Collection="{Binding Source={StaticResource CoursesCVS}}" />
</CompositeCollection>
</ComboBox.ItemsSource>-->
</ComboBox>
<TextBlock Text="{Binding SelectedCourse.CourseName}" Grid.Row="3" Margin="15,5,0,5" Width="200" />
</Grid>
viewmodel and supporting classes:
public class Course
{
public int CourseID { get; set; }
public string CourseName { get; set; }
public string Major { get; set; }
}
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
_courses = new List<Course>();
_courses.Add(new Course { CourseID = 1, CourseName = "Math 107", Major = "Math" });
_courses.Add(new Course { CourseID = 1, CourseName = "Biology 110", Major = "Biology" });
_courses.Add(new Course { CourseID = 1, CourseName = "Chemistry 123", Major = "Chemistry" });
_courses.Add(new Course { CourseID = 1, CourseName = "Spanish 112", Major = "Biology" });
_courses.Add(new Course { CourseID = 1, CourseName = "Molecular 114", Major = "Biology" });
}
private List<Course> _courses;
public List<Course> Courses
{
get { return _courses; }
set { _courses = value; OnPropertyChanged(); }
}
private Course _selectedCourse;
public Course SelectedCourse
{
get { return _selectedCourse; }
set { _selectedCourse = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class CourseConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is Course)
return value;
else
return null;
}
}
Is there something that I am missing to make it work with and without grouping. For removing grouping, I remove inline ItemSource declaration and use the commented code. This shows the blank.
In WPF, we work with data items, not UI elements. We declare DataTemplates to define what our data items should look like in any UI container. Using this method, we let the Framework take care of displaying the UI and we concentrate on the data. So, to display an empty UI element in WPF, you just have to add an empty data item into your collection and let the DataTemplate do its work:
_courses.Add(new Course());
This would be rendered simply as an empty item as it has no data to be displayed in any data bound controls. So try declaring a DataTemplate in the ComboBox.ItemTemplate property, or even just setting the DisplayMemberPath to the relevant Course property if you only want to display one value.
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");
}
}
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.