Environment: WinRt / XAML / C#
I am trying to do a two way binding for a float type property. However if that is a nullable type (float? or Nullable) the binding doesnt work.
I have event applied converters for this, and it still doesnt show any sign of bindability.
C#: ViewModel
public class MyViewModel : INotifyPropertyChanged
{
private float _quantity1;
public float Quantity1
{
get
{
return this._quantity1;
}
set
{
this._quantity1 = value;
RaisePropertyChanged("Quantity1");
}
}
private float? _quantity2;
public float? Quantity2
{
get
{
return this._quantity2;
}
set
{
this._quantity2 = value;
RaisePropertyChanged("Quantity2");
}
}
private Nullable<float> _quantity3;
public Nullable<float> Quantity3
{
get
{
return this._quantity3;
}
set
{
this._quantity3 = value;
RaisePropertyChanged("Quantity3");
}
}
public MyViewModel()
{
this.Quantity1 = 100.01F;
this.Quantity2 = 200.02F;
this.Quantity3 = 300.03F;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null))
{
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
C# : Converter:
public sealed class NullableFloatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value == null)
return 0F;
else
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value != null)
return value;
else
return 0;
}
}
XAML:
<Page
x:Class="Test_Binding.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Test_Binding"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<local:NullableFloatConverter x:Key="nullConverter" />
</Page.Resources>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Quantity1: " Width="150" />
<TextBox Text="{Binding Quantity1, Mode=TwoWay}" />
<TextBlock Text="{Binding Quantity1}" />
</StackPanel>
<!-- the second text block doesnt get an updated value -->
<StackPanel Orientation="Horizontal">
<TextBlock Text="Quantity2: " Width="150"/>
<TextBox Text="{Binding Quantity2, Mode=TwoWay, Converter={StaticResource nullConverter}}" />
<TextBlock Text="{Binding Quantity2, Converter={StaticResource nullConverter}}" />
</StackPanel>
<!-- the second text block doesnt get an updated value -->
<StackPanel Orientation="Horizontal">
<TextBlock Text="Quantity2 (No Converter): " Width="150"/>
<TextBox Text="{Binding Quantity2, Mode=TwoWay}" />
<TextBlock Text="{Binding Quantity2}" />
</StackPanel>
<!-- the second text block doesnt get an updated value -->
<StackPanel Orientation="Horizontal">
<TextBlock Text="Quantity3: " Width="150"/>
<TextBox Text="{Binding Quantity3, Mode=TwoWay}" />
<TextBlock Text="{Binding Quantity3}" />
</StackPanel>
</StackPanel>
</Grid>
Only the First text block gets updated (i.e. for Quantity1). I cant get the others (Quantity2 & Quantity3) to get updated.
Any suggestions ?
I think the convertBack method wasnt upto scratch, which i got from stackoverflow.com/questions/15406336/databind-a-nullable-type-in-xaml-windows-8-store-app
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
string s = value as string;
float result;
if (!string.IsNullOrWhiteSpace(s) && float.TryParse(s, out result))
{
return result;
}
return null;
}
Related
I have a WPF window that uses multiple viewmodel objects as its DataContext. The window has a control that binds to a property that exists only in some of the viewmodel objects. How can I bind to the property if it exists (and only if it exists).
I am aware of the following question/answer: MVVM - hiding a control when bound property is not present. This works, but gives me a warning. Can it be done without the warning?
Thanks!
Some example code:
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="1" Name="ListView" Margin="25,0,25,0" ItemsSource="{Binding Path=Lst}"
HorizontalContentAlignment="Center" SelectionChanged="Lst_SelectionChanged">
</ListBox>
<local:SubControl Grid.Row="3" x:Name="subControl" DataContext="{Binding Path=SelectedVM}"/>
</Grid>
SubControl Xaml:
<UserControl x:Class="WpfApplication1.SubControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffAlways}"/>
<CheckBox IsChecked="{Binding Path=Always}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
<StackPanel Grid.Row="3" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffSometimes}"/>
<CheckBox IsChecked="{Binding Path=Sometimes}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
</Grid>
MainWindow Code Behind:
public partial class MainWindow : Window
{
ViewModel1 vm1;
ViewModel2 vm2;
MainViewModel mvm;
public MainWindow()
{
InitializeComponent();
vm1 = new ViewModel1();
vm2 = new ViewModel2();
mvm = new MainViewModel();
mvm.SelectedVM = vm1;
DataContext = mvm;
}
private void Lst_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox lstBx = sender as ListBox;
if (lstBx != null)
{
if (lstBx.SelectedItem.Equals("VM 1"))
mvm.SelectedVM = vm1;
else if (lstBx.SelectedItem.Equals("VM 2"))
mvm.SelectedVM = vm2;
}
}
}
MainViewModel (DataContext of MainWindow):
public class MainViewModel : INotifyPropertyChanged
{
ObservableCollection<string> lst;
ViewModelBase selectedVM;
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel()
{
Lst = new ObservableCollection<string>();
Lst.Add("VM 1");
Lst.Add("VM 2");
}
public ObservableCollection<string> Lst
{
get { return lst; }
set
{
lst = value;
OnPropertyChanged("Lst");
}
}
public ViewModelBase SelectedVM
{
get { return selectedVM; }
set
{
if (selectedVM != value)
{
selectedVM = value;
OnPropertyChanged("SelectedVM");
}
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
ViewModel1 (with sometimes property):
public class ViewModel1 : ViewModelBase, INotifyPropertyChanged
{
private bool _always;
private string _onOffAlways;
private bool _sometimes;
private string _onOffSometimes;
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel1()
{
_always = false;
_onOffAlways = "Always Off";
_sometimes = false;
_onOffSometimes = "Sometimes Off";
}
public bool Always
{
get { return _always; }
set
{
_always = value;
if (_always)
OnOffAlways = "Always On";
else
OnOffAlways = "Always Off";
OnPropertyChanged("Always");
}
}
public string OnOffAlways
{
get { return _onOffAlways; }
set
{
_onOffAlways = value;
OnPropertyChanged("OnOffAlways");
}
}
public bool Sometimes
{
get { return _sometimes; }
set
{
_sometimes = value;
if (_sometimes)
OnOffSometimes = "Sometimes On";
else
OnOffSometimes = "Sometimes Off";
OnPropertyChanged("Sometimes");
}
}
public string OnOffSometimes
{
get { return _onOffSometimes; }
set
{
_onOffSometimes = value;
OnPropertyChanged("OnOffSometimes");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
ViewModel2 (without Sometimes property):
public class ViewModel2 : ViewModelBase, INotifyPropertyChanged
{
private bool _always;
private string _onOffAlways;
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel2()
{
_always = false;
_onOffAlways = "Always Off";
}
public bool Always
{
get { return _always; }
set
{
_always = value;
if (_always)
OnOffAlways = "Always On";
else
OnOffAlways = "Always Off";
OnPropertyChanged("Always");
}
}
public string OnOffAlways
{
get { return _onOffAlways; }
set
{
_onOffAlways = value;
OnPropertyChanged("OnOffAlways");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public class AlwaysVisibleConverter : IValueConverter
{
#region Implementation of IValueConverter
public object Convert(object value,
Type targetType, object parameter, CultureInfo culture)
{
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
There are many different ways one could approach your scenario. For what it's worth, the solution you already have seems reasonable to me. The warning you get (I presume you are talking about the error message output to the debug console) is reasonably harmless. It does imply a potential performance issue, as it indicates WPF is recovering from an unexpected condition. But I would expect the cost to be incurred only when the view model changes, which should not be frequent enough to matter.
Another option, which is IMHO the preferred one, is to just use the usual WPF data templating features. That is, define a different template for each view model you expect, and then let WPF pick the right one according to the current view model. That would look something like this:
<UserControl x:Class="TestSO46736914MissingProperty.UserControl1"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:l="clr-namespace:TestSO46736914MissingProperty"
mc:Ignorable="d"
Content="{Binding}"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate DataType="{x:Type l:ViewModel1}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffAlways}"/>
<CheckBox IsChecked="{Binding Path=Always}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
<StackPanel Grid.Row="3" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffSometimes}"/>
<CheckBox IsChecked="{Binding Path=Sometimes}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type l:ViewModel2}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffAlways}"/>
<CheckBox IsChecked="{Binding Path=Always}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
</Grid>
</DataTemplate>
</UserControl.Resources>
</UserControl>
I.e. just set the Content of your UserControl object to the view model object itself, so that the appropriate template is used to display the data in the control. The template for the view model object that doesn't have the property, doesn't reference that property and so no warning is generated.
Yet another option, which like the above also addresses your concern about the displayed warning, is to create a "shim" (a.k.a. "adapter") object that mediates between the unknown view model type and a consistent one the UserControl can use. For example:
class ViewModelWrapper : NotifyPropertyChangedBase
{
private readonly dynamic _viewModel;
public ViewModelWrapper(object viewModel)
{
_viewModel = viewModel;
HasSometimes = viewModel.GetType().GetProperty("Sometimes") != null;
_viewModel.PropertyChanged += (PropertyChangedEventHandler)_OnPropertyChanged;
}
private void _OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
_RaisePropertyChanged(e.PropertyName);
}
public bool Always
{
get { return _viewModel.Always; }
set { _viewModel.Always = value; }
}
public string OnOffAlways
{
get { return _viewModel.OnOffAlways; }
set { _viewModel.OnOffAlways = value; }
}
public bool Sometimes
{
get { return HasSometimes ? _viewModel.Sometimes : false; }
set { if (HasSometimes) _viewModel.Sometimes = value; }
}
public string OnOffSometimes
{
get { return HasSometimes ? _viewModel.OnOffSometimes : null; }
set { if (HasSometimes) _viewModel.OnOffSometimes = value; }
}
private bool _hasSometimes;
public bool HasSometimes
{
get { return _hasSometimes; }
private set { _UpdateField(ref _hasSometimes, value); }
}
}
This object uses the dynamic feature in C# to access the known property values, and uses reflection on construction to determine whether or not it should try to access the Sometimes (and related OnOffSometimes) property (accessing the property via the dynamic-typed variable when it doesn't exist would throw an exception).
It also implements the HasSometimes property so that the view can dynamically adjust itself accordingly. Finally, it also proxies the underlying PropertyChanged event, to go along with the delegated properties themselves.
To use this, a little bit of code-behind for the UserControl is needed:
partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public ViewModelWrapper ViewModelWrapper { get; private set; }
public UserControl1()
{
DataContextChanged += _OnDataContextChanged;
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
private void _OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ViewModelWrapper = new ViewModelWrapper(DataContext);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ViewModelWrapper)));
}
}
With this, the XAML is mostly like what you originally had, but with a style applied to the optional StackPanel element that has a trigger to show or hide the element according to whether the property is present or not:
<UserControl x:Class="TestSO46736914MissingProperty.UserControl1"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:l="clr-namespace:TestSO46736914MissingProperty"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid DataContext="{Binding ViewModelWrapper, RelativeSource={RelativeSource AncestorType=UserControl}}">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffAlways}"/>
<CheckBox IsChecked="{Binding Path=Always}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
<StackPanel Grid.Row="3" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<StackPanel.Style>
<p:Style TargetType="StackPanel">
<p:Style.Triggers>
<DataTrigger Binding="{Binding HasSometimes}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</p:Style.Triggers>
</p:Style>
</StackPanel.Style>
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffSometimes}"/>
<CheckBox IsChecked="{Binding Path=Sometimes}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
</Grid>
</UserControl>
Note that the top-level Grid element's DataContext is set to the UserControl's ViewModelWrapper property, so that the contained elements use that object instead of the view model assigned by the parent code.
(You can ignore the p: XML namespace…that's there only because Stack Overflow's XAML formatting gets confused by <Style/> elements that use the default XML namespace.)
While I in general would prefer the template-based approach, as the idiomatic and inherently simpler one, this wrapper-based approach does have some advantages:
It can be used in situations where the UserControl object is declared in an assembly different from the one where the view model types are declared, and where the latter assembly cannot be referenced by the former.
It removes the redundancy that is required by the template-based approach. I.e. rather than having to copy/paste the shared elements of the templates, this approach uses a single XAML structure for the entire view, and shows or hides elements of that view as appropriate.
For completeness, here is the NotifyPropertyChangedBase class used by the ViewModelWrapper class above:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
_RaisePropertyChanged(propertyName);
}
protected void _RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
For what it's worth, I prefer this approach to re-implementing the INotifyPropertyChanged interface in each model object. The code is a lot simpler and easier to write, simpler to read, and less prone to errors.
Here's a fairly simple solution using DataTriggers and a custom converter:
<Style TargetType="CheckBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={HasPropertyConverter PropertyName=Sometimes}}" Value="True">
<Setter Property="IsChecked" Value="{Binding Sometimes}" />
</DataTrigger>
</Style.Triggers>
</Style>
The converter:
public class HasPropertyConverter : IValueConverter
{
public string PropertyName { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (string.IsNullOrWhiteSpace(PropertyName))
return DependencyProperty.UnsetValue;
return value?.GetType().GetProperty(PropertyName) != null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
public class HasPropertyConverterExtension : MarkupExtension
{
public string PropertyName { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
=> new HasPropertyConverter { PropertyName = PropertyName };
}
This question already has answers here:
How to use DataTemplateSelector with ContentControl to display different controls based on the view-model?
(2 answers)
Closed 4 years ago.
I'm trying to write a simple dialog that would accept a value in a SpinEdit or a text in a TextEdit. I'm using multiple VMs and I made a selector that should view a proper control based on the logic in the c++/cli file.
XAML:
xmlns:local="clr-namespace:asd"
Title="{Binding Path=Title, Mode=OneTime}"
<dx:DXWindow.Resources>
<DataTemplate x:Key="TInputValueVM" DataType="{x:Type local:TInputValueVM}">
<dxe:SpinEdit Height="23" Width="200"
Text="{Binding Value, Mode=TwoWay}"
Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}" />
</DataTemplate>
<DataTemplate x:Key="TInputTextVM" DataType="{x:Type local:TInputTextVM}">
<dxe:TextEdit Height="23" Width="200"
Text="{Binding Value, Mode=TwoWay}"
MaskType="RegEx" Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}"/>
</DataTemplate>
<local:PropertyDataTemplateSelector x:Key="templateSelector"
DataTemplate_Value="{StaticResource TInputValueVM}"
DataTemplate_Text="{StaticResource TInputTextVM}" />
</dx:DXWindow.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" >
<Label x:Uid="Label" MinHeight="24" MinWidth="60" Content="Value" />
<ContentControl Content="{Binding Path=Whoami}" ContentTemplateSelector="{StaticResource templateSelector}" />
</StackPanel>
<StackPanel Grid.Row="1" x:Uid="OKCancel_Buttons" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<Button Height="23" x:Name="OK_Button" Click="OK_Click" Content="OK" IsDefault="True" HorizontalAlignment="Right" MinWidth="95" />
<Button Height="23" x:Name="Cancel_Button" Click="Cancel_Click" Content="Cancel" HorizontalAlignment="Right" MinWidth="95" />
</StackPanel>
</Grid>
In c# I have a base VM and two VMS that extend it, one for values and one for text. The rest of the properties stay the same.
C#
namespace asd
{
public class TInputBaseVM : ViewModelBase
{
private string m_sTitle;
private string m_sMask;
private int m_nInputLenght;
private string m_sWhoami;
public TInputBaseVM(string A_sTitle, string A_sMask, int A_nInputLength)
{
m_sTitle = A_sTitle;
m_sMask = A_sMask;
m_nInputLenght = A_nInputLength;
}
protected string Title
{
get { return m_sTitle; }
set { SetProperty(ref m_sTitle, value, () => Title); }
}
protected string Mask
{
get { return m_sMask; }
set { SetProperty(ref m_sMask, value, () => Mask); }
}
protected int InputLength
{
get { return m_nInputLenght; }
set { SetProperty(ref m_nInputLenght, value, () => InputLength); }
}
protected string Whoami
{
get { return m_sWhoami; }
set { SetProperty(ref m_sWhoami, value, () => Whoami); }
}
}
public class TInputValueVM : TInputBaseVM
{
public TInputValueVM(string A_sTitle, string A_sMask, int A_nInputLength, double A_nValue) : base(A_sTitle, A_sMask, A_nInputLength)
{
Value = A_nValue;
Whoami = "Value";
}
private double m_nValue;
public double Value
{
get { return m_nValue; }
set { SetProperty(ref m_nValue, value, () => Value); }
}
}
public class TInputTextVM : TInputBaseVM
{
public TInputTextVM(string A_sTitle, string A_sMask, int A_nInputLength, string A_sValue) : base(A_sTitle, A_sMask, A_nInputLength)
{
Value = A_sValue;
Whoami = "Text";
}
private string m_sValue;
public string Value
{
get { return m_sValue; }
set { SetProperty(ref m_sValue, value, () => Value); }
}
}
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DataTemplate_Value { get; set; }
public DataTemplate DataTemplate_Text { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var selector = item as string;
if(selector == "Value")
return DataTemplate_Value;
return DataTemplate_Text;
}
}
}
In c++/cli I create an object of a proper VM and I'd like the WPF to automatically update the view to either spinedit or textedit, however I'm not sure how to properly bind the properties from the C#. If I explicitly type 'Value' in the Content property of the ContentControl then it displays the spinEdit but I don't know how to bind it so it automatically takes the correct property.
EDIT: I'm adding c++/cli code to show how I choose different VMs
C++/cli:
bool TSignalNumberPositionDialogCLR::StartDialog(TSignalNumberPositionSupport& A_Attributes, HWND A_hwndParent, LPTSTR String)
{
try
{
TInputValueVM ^oExchange_Value;
TInputTextVM ^oExchange_Text;
int inputFormat = A_Attributes.GetInputFormat();
if(inputFormat)
oExchange_Text = gcnew TInputTextVM(gcnew System::String(A_Attributes.GetTitle()), gcnew System::String(A_Attributes.GetMask()),
A_Attributes.GetInputLength(), gcnew System::String(A_Attributes.GetInitialText()));
else
oExchange_Value = gcnew TInputValueVM(gcnew System::String(A_Attributes.GetTitle()), gcnew System::String(A_Attributes.GetMask()),
A_Attributes.GetInputLength(), A_Attributes.GetInitialValue());
Dialogs::TSignalNumberPositionDialog^ dialog = gcnew Dialogs::TSignalNumberPositionDialog();
if(inputFormat)
dialog->DataContext = oExchange_Text;
else
dialog->DataContext = oExchange_Value;
dialog->ShowDialog();
if(dialog->DialogResult)
{
CString nValue;
if(inputFormat)
nValue = oExchange_Text->Value;
else
nValue = ((Decimal)oExchange_Value->Value).ToString("F2", CultureInfo::InvariantCulture);
A_Attributes.UpdateValue(nValue, String, A_Attributes.GetInputLength());
return true;
}
return false;
}
catch(Exception^ e)
{
e;
}
}
based on the 'inputFormat' variable I want to display different controls in the dialog.
EDIT: Based on #Clemens comments I got rid of the selector sectionand the x:Key property in the DataTemplates. I changed the content opf the Content property to Content="{Binding}" and it somehow works. The moment I create a VM it selects the correct one.
Based on your comment. Let me improve my answer. As you are facing issue in VM selection. so plesae concentrate how I assigned VM to datatemplate. Although it is done in very basic way, you can handle it if you you are using MVVM packages.
I have created 2 data template and 2 vms and each vm is bound to datatemplate. To verify, I have a combobox, which will select datatemplate based on selected value.
Here is Sample VM
public class VM : System.ComponentModel.INotifyPropertyChanged
{
private string title;
private SolidColorBrush background;
public string Title { get => title; set { title = value; RaisePropertyChanged(); } }
public SolidColorBrush Background { get => background; set { background = value; RaisePropertyChanged(); } }
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class VM1: VM
{
public VM1()
{
Title = "This is VM1";
Background = Brushes.Yellow;
}
}
public class VM2: VM
{
public VM2()
{
Title = "This is VM2";
Background = Brushes.Orange;
}
}
Now check for resources
<local:VM1 x:Key="VM1"/>
<local:VM2 x:Key="VM2"/>
<DataTemplate x:Key="DT1">
<Grid DataContext="{StaticResource VM1}">
<TextBlock Text="{Binding Title}" Background="{Binding Background}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="DT2">
<Grid DataContext="{StaticResource VM2}">
<TextBlock Text="{Binding Title}" Background="{Binding Background}"/>
</Grid>
</DataTemplate>
<Style TargetType="ContentControl" x:Key="contentStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cmbo, Path=SelectedValue}" Value="Template1">
<Setter Property="ContentTemplate" Value="{StaticResource DT1}" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=cmbo, Path=SelectedValue}" Value="Template2">
<Setter Property="ContentTemplate" Value="{StaticResource DT2}" />
</DataTrigger>
</Style.Triggers>
</Style>
and finally I have combobox and content control just to verify
<ComboBox Name="cmbo"/>
<ContentControl Style="{StaticResource contentStyle}"/>
where cmbo.ItemsSource = new List { "Template1", "Template2" };
Hope you got the point
I am trying to figure out how to enable and disable textboxes with radio buttons and data binding. It seems like I should be able to bind the textbox IsEnabled to a boolean and modify that value but I can't quite get it to work. I want to have several sets of radio buttons and textboxes so I want a generic way to handle the problem. I tried to use a converter but since I am not using any x:Names I am not sure those would be helpful in this case. I can get them to enable/disable the radio buttons but that is not what I want to do. My code shows mostly the solution I am trying for the first textbox.
Xaml Code
<Grid>
<StackPanel>
<RadioButton GroupName="grp1" Content="Enable TextBox" IsEnabled="{Binding Bttn1, Mode=TwoWay}" IsChecked="{Binding Bttn1}" Checked="RadioButton_Checked" Unchecked="RadioButton_UnChecked" />
<RadioButton GroupName="grp1" Content="Disable TextBox" IsEnabled="{Binding Bttn2}" />
<TextBox IsEnabled="{Binding Txtbx1, Mode=TwoWay}" BindingGroup="{Binding grp1}" ></TextBox>
<RadioButton GroupName="grp2" Content="Enable TextBox" IsEnabled="{Binding Bttn3, Mode=TwoWay}" IsChecked="{Binding Bttn3}" Checked="RadioButton_Checked" Unchecked="RadioButton_UnChecked" />
<RadioButton GroupName="grp2" Content="Disable TextBox" IsEnabled="{Binding Bttn4}" />
<TextBox IsEnabled="{Binding Txtbx2, Mode=TwoWay}" BindingGroup="{Binding grp2}" ></TextBox>
</StackPanel>
</Grid>
ViewModel Code
private bool _bttn1;
private bool _bttn2;
private bool _bttn3;
private bool _bttn4;
private bool _txtbx1;
private bool _txtbx2;
public bool Bttn1
{
get
{
return(_bttn1);
}
set
{
_bttn1 = value;
_txtbx1 = false;
RaisePropertyChanged(Txtbx1.ToString());
}
}
public bool Bttn2
{
get
{
return (_bttn1);
}
set
{
_bttn1 = value;
_txtbx1 = false;
RaisePropertyChanged(Txtbx1.ToString());
}
}
public bool Txtbx1
{
get
{
return (_txtbx1);
}
set
{
_txtbx1 = false;
}
}
Further down I have
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
private void RadioButton_Checked(object sender, RoutedEventArgs e)
{
Handle(sender as RadioButton);
}
private void RadioButton_UnChecked(object sender, RoutedEventArgs e)
{
Handle(sender as RadioButton);
}
void Handle(RadioButton radioButton)
{
bool flag = radioButton.IsChecked.Value;
this.Title = "IsChecked = " + flag.ToString();
}
public class RadioConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return !(bool)parameter;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return !(bool)value ? parameter : null;
}
}
In case you want to enable and disable textboxes with radio buttons, this solution works:
<Grid>
<StackPanel>
<RadioButton x:Name="RB1" GroupName="grp1" Content="Enable TextBox" />
<RadioButton GroupName="grp1" Content="Disable TextBox" />
<TextBox IsEnabled="{Binding IsChecked, ElementName=RB1}" ></TextBox>
<RadioButton x:Name="RB2" GroupName="grp2" Content="Enable TextBox" />
<RadioButton GroupName="grp2" Content="Disable TextBox" />
<TextBox IsEnabled="{Binding IsChecked, ElementName=RB2}" ></TextBox>
</StackPanel>
I have been facing a issue in updating the XAML in windows phone 8... the properties are binded in XAML with the viewModel, propertyChange is triggered and it changes the values of the properties. but the property members in XAML are only updated once at the beginning since then it does not update any thing in XAML... Although the properties continue to change in ViewModel.... the properties belong to a LIST of observation collection and finally Observation Collection is binded to LongListSelector
I have changed the binding Mode to "two Way" but useless i have pasted the code below.
Looking forward for help.
ViewModel:
private string _description;
public string description
{
set
{
_description = value;
RaisePropertyChanged("_description");
}
get
{
return _description;
}
}
private double _progress_bar_Value;
public double progress_bar_Value
{
set
{
_progress_bar_Value = value;
RaisePropertyChanged("_progress_bar_Value");
}
get
{
return _progress_bar_Value; //= ProfileSetting.ProfileTab_DOB;
}
}
private double _Total_Bytes;
public double Total_Bytes
{
set
{
_Total_Bytes = value;
RaisePropertyChanged("_Total_Bytes");
}
get
{
return _Total_Bytes;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
`
>
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,0" Orientation="Vertical"
>
<TextBlock Text="{Binding description}"
FontSize="18"
TextWrapping="Wrap"
Foreground="White" x:Name="Totalsize"
/>
<ProgressBar x:Name="Download_progressBar"
IsIndeterminate="False"
Maximum="100"
Height="10"
Width="400"
Value="{Binding progress_bar_Value}"
Foreground="White"
/>
<TextBlock Text="{Binding Bytes_received}"
FontSize="18"
TextWrapping="Wrap"
Foreground="White"
x:Name="Total_received"
/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>`
Raise Property Changed on the public property not backing field (as commented by #HighCore)
Im having text box and list view and when you are pressing on the button
you are the list view is filled with data ,currently the list view is under the button and the text box and
always is there and filled after you press on the button.
There is a way to hide the list view from the page until you press on the button and requesting the data?
public class ModelView
{
public ModelView()
{
GetServiceCollection = new ObservableCollection<string>();
}
bool isDataLoaded = false;
MyCommand goCommand;
public ICommand GoCommand
{
get { return goCommand ?? (goCommand = new MyCommand(() => OnGoCommand(), () => !isDataLoaded)); }
}
public ObservableCollection<string> GetServiceCollection { get; set; }
void OnGoCommand()
{
GetServiceCollection.Clear();
foreach (var item in _configServiceModel.CollectList)
{
GetServiceCollection.Add(item);
}
isDataLoaded = true;
goCommand.RaiseCanExecuteChanged();
}
......
The xaml
<Button Content="Go" Grid.Column="3" Grid.Row="1" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="75" Height="21.96" Command="{Binding GoCommand}"/>
<ListView Grid.Column="2" HorizontalAlignment="Center" Height="230"
Margin="5,20,0,0" Grid.Row="2" VerticalAlignment="Top" Width="330"
ItemsSource="{Binding GetCollection}" }" >
}
ViewModel
public class ConfigModelView:INotifyPropertyChanged
{
public ConfigModelView()
{
GetServiceCollection=new ObservableCollection<string>();
}
bool isDataLoaded;
public bool IsDataLoaded
{
get { return isDataLoaded; }
set { isDataLoaded = value; OnPropertyChanged("IsDataLoaded"); }
}
MyCommand goCommand;
public ICommand GoCommand
{
get{return goCommand ?? (goCommand=new MyCommand(()=>Command(),()=>!isDataLoaded));}
}
public ObservableCollection<string> GetServiceCollection{get;set;}
void Command()
{
foreach (var item in _configServiceModel.CollectList)
{
GetServiceCollection.Add(item);
}
isDataLoaded = true;
OnPropertyChanged("IsDataLoaded");
goCommand.RaiseCanExecuteChanged();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
BooleanToVisibilityConverter
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool)
{
if ((bool)value)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
xaml
<Window x:Class="WpfApplication3.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication3"
Title="Window1" Height="300" Width="800">
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</Window.Resources>
<StackPanel>
<Button Content="Go" Grid.Column="3" Grid.Row="1" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="75" Height="21.96" Command="{Binding GoCommand}"/>
<ListView Grid.Column="2" HorizontalAlignment="Center" Height="230"
Margin="5,20,0,0" Grid.Row="2" VerticalAlignment="Top" Width="330"
Visibility="{Binding IsDataLoaded,
Converter= {StaticResource BoolToVisibilityConverter}}"
ItemsSource="{Binding GetCollection}" />
</StackPanel>
The best bet here would be to create another property on the ViewModel that you bind the Visibility of the ListView to. In the GoCommand implementation, set this property to visible.
As a side note, your ViewModel doesn't implement INotifyPropertyChanged, so you'll need to do that as well to have the visiblity update when the property is changed:
private Visibility listViewVisibility;
public Visibility ListViewVisibility
{
get { return listViewVisibility; }
set
{
if (this.listViewVisibility == value)
return;
this.listViewVisibility = value;
this.OnPropertyChanged("ListViewVisibility");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
The xaml:
<ListView Grid.Column="2" HorizontalAlignment="Center" Height="230"
Margin="5,20,0,0" Grid.Row="2" VerticalAlignment="Top" Width="330"
Visibility="{Binding ListViewVisibility}"
ItemsSource="{Binding GetCollection}" />
Well load your form then in the put "listView1.hide()".
Then create your button event.
Type "listView1.show()".
P.S. you can also set all of those values in your c# code.