I am trying to hide an item in the Combobox when it has been selected and this is how my code looks like right now:
VeiwModel.cs
public class SortList
{
public string Key { get; set; }
public string Value { get; set; }
public bool IsSelectable { get; set; }
}
private void InitSortList()
{
ObservableCollection<SortList> sl = new ObservableCollection<SortList>();
foreach(var i in defaultSortList)
{
SortList s = new SortList();
s.Key = i.Key.ToString();
s.Value = i.Value.ToString();
s.IsSelectable = false;
sl.Add(s);
}
_items = sl;
}
private ObservableCollection<SortList> _items = new ObservableCollection<SortList>();
public ObservableCollection<SortList> Items
{
get {
return _items; }
}
private SortList _selectedSort;
public SortList SelectedItem
{
get { return _selectedSort; }
set
{
if(_selectedSort != value)
{
_selectedSort = value;
_selectedSort.IsSelectable = false;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
}
MainPage.xaml
<ComboBox Header="Sort 1" HorizontalAlignment="Stretch"
Name="Sort_1" SelectionChanged="comboSelectionChanged"
ItemsSource="{Binding Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
SelectedValuePath="Key"
DisplayMemberPath="Value"
>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem" BasedOn="ComboBoxIem">
<Setter
Property="IsEnabled"
Value="{Binding Items.IsSelectable, Mode=TwoWay}" />
//Binding IsSelectable doesnt work either
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
I am not sure how the Binding part works on the Setter property as I think it's not getting the IsSelectable property from the Items class....
Please refer to document here, UWP does not support bindings in Style Setters. It will not effect when you binding ItemContainerStyle style.
Windows Presentation Foundation (WPF) and Microsoft Silverlight supported the ability to use a Binding expression to supply the Value for a Setter in a Style. The Windows Runtime doesn't support a Binding usage for Setter.Value (the Binding won't evaluate and the Setter has no effect, you won't get errors, but you won't get the desired result either). When you convert XAML styles from Windows Presentation Foundation (WPF) or Microsoft Silverlight XAML, replace any Binding expression usages with strings or objects that set values, or refactor the values as shared {StaticResource} markup extension values rather than Binding -obtained values.
For this scenario, A workaround could be a helper class with attached properties for the source paths of the bindings. It will create the binding expression in code behind in a PropertyChangedCallback of the helper property.
I have edited your code and xaml, please refer to the following code the implement.
<Page.DataContext>
<local:ViewModel />
</Page.DataContext>
<Grid>
<ComboBox
Name="Sort_1"
HorizontalAlignment="Stretch"
DisplayMemberPath="Value"
Header="Sort 1"
ItemsSource="{Binding Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
SelectedValuePath="Key"
SelectionChanged="comboSelectionChanged">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="local:BindingHelper.IsEnable" Value="IsSelectable" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</Grid>
C# Code
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void comboSelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
public class BindingHelper
{
public static string GetIsEnable(DependencyObject obj)
{
return (string)obj.GetValue(IsEnableProperty);
}
public static void SetIsEnable(DependencyObject obj, string value)
{
obj.SetValue(IsEnableProperty, value);
}
// Using a DependencyProperty as the backing store for IsEnable. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsEnableProperty =
DependencyProperty.RegisterAttached("IsEnable", typeof(string), typeof(BindingHelper), new PropertyMetadata(null, GridBindingPathPropertyChanged));
private static void GridBindingPathPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var propertyPath = e.NewValue as string;
if (propertyPath != null)
{
var bindingProperty =
e.Property == IsEnableProperty
? ComboBoxItem.IsEnabledProperty
: null;
BindingOperations.SetBinding(
obj,
bindingProperty,
new Binding { Path = new PropertyPath(propertyPath) });
}
}
}
public class ViewModel : INotifyPropertyChanged
{
public class SortList : INotifyPropertyChanged
{
public string Key { get; set; }
public string Value { get; set; }
private bool _isSelectable;
public bool IsSelectable
{
get { return _isSelectable; }
set
{
_isSelectable = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string PropertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
public ViewModel()
{
defaultSortList = new Dictionary<string, string>();
defaultSortList.Add("0", "item");
defaultSortList.Add("1", "item1");
defaultSortList.Add("2", "item2");
defaultSortList.Add("3", "item3");
InitSortList();
}
private Dictionary<string, string> defaultSortList;
private void InitSortList()
{
ObservableCollection<SortList> sl = new ObservableCollection<SortList>();
foreach (var i in defaultSortList)
{
SortList s = new SortList();
s.Key = i.Key.ToString();
s.Value = i.Value.ToString();
s.IsSelectable = true;
sl.Add(s);
}
_items = sl;
}
private ObservableCollection<SortList> _items = new ObservableCollection<SortList>();
public ObservableCollection<SortList> Items
{
get
{
return _items;
}
}
private SortList _selectedSort;
public event PropertyChangedEventHandler PropertyChanged;
public SortList SelectedItem
{
get { return _selectedSort; }
set
{
if (_selectedSort != value)
{
_selectedSort = value;
_selectedSort.IsSelectable = false;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
}
}
Related
I would like to have a converter system for my Xamarin and WPF project. I don't want to save any units in the database, so I want directly convert the textbox-values when user change the unit.
I made public a few Observable Collections like;
public class AreaList : ObservableCollection<Unit>
{
public AreaList() : base()
{
Add(new Unit("mm²"));
Add(new Unit("cm²"));
Add(new Unit("dm²"));
Add(new Unit("m²"));
}
}
public class Unit
{
private string name;
public Unit(string name)
{
this.name = name;
}
public string Name
{
get { return name; }
set { name = value; }
}
}
In the View i bind the collection to my combo box. I gave my TextBox the name of his binding property(Text="{Binding TxtBoxValue}" => x:Name="TxtBoxValue"). The ConvertUnitValueCommand set this name as a string in the view model to know which variable the converter function should use when the unit is changed.
View
<UserControl.Resources>
<c:AreaList x:Key="AreaListData" />
</UserControl.Resources>
<TextBox x:Name="TxtBoxValue"
Text="{Binding Mode=TwoWay, Path=TxtBoxValue, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
<ComboBox IsSynchronizedWithCurrentItem="True"
IsEditable="False"
DisplayMemberPath="Name"
SelectedItem="{Binding Unit,Mode=OneWayToSource}"
ItemsSource="{Binding Source={StaticResource AreaListData}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding ConvertUnitValueCommand}"
CommandParameter="{Binding ElementName=TxtBoxValue, Path=Name}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
ViewModel
private string ConvertControlName;
private void ConvertUnitValue(object obj)
{
ConvertControlName = obj.ToString();
}
public Unit Unit
{
get => Get<Unit>();
set
{
if (ConvertControlName != null)
{
FieldInfo variable = this.GetType().GetField(ConvertControlName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic);
//Get the Value from setted Binding Variable
double oldValue = (double)variable.GetValue(this);
//Convert the value
if (oldValue > 0)
{
double newValue = Converts.ConvertUnitValue(Unit, value, oldValue);
variable.SetValue(this, newValue);
}
Set(value);
}
}
Maybe anyone can give me some inspiration to do it better.
The following example normalizes the user input to the base unit m²:
Unit.cs
public class Unit
{
public Unit(string name, decimal baseFactor)
{
this.Name = name;
this.BaseFactor = baseFactor;
}
#region Overrides of Object
/// <inheritdoc />
public override string ToString() => this.Name;
#endregion
public string Name { get; set; }
public decimal BaseFactor { get; set; }
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.Units = new List<Unit>()
{
new Unit("mm²", (decimal) (1 / Math.Pow(1000, 2))),
new Unit("cm²", (decimal) (1 / Math.Pow(100, 2))),
new Unit("dm²", (decimal) (1 / Math.Pow(10, 2))),
new Unit("m²", 1)
};
}
private void NormalizeValue()
{
this.NormalizedValue = this.UnitValue * this.SelectedUnit.BaseFactor;
}
private List<Unit> units;
public List<Unit> Units
{
get => this.units;
set
{
this.units = value;
OnPropertyChanged();
}
}
private Unit selectedUnit;
public Unit SelectedUnit
{
get => this.selectedUnit;
set
{
this.selectedUnit = value;
OnPropertyChanged();
NormalizeValue();
}
}
private decimal unitValue;
public decimal UnitValue
{
get => this.unitValue;
set
{
this.unitValue = value;
OnPropertyChanged();
NormalizeValue();
}
}
private decimal normalizedValue;
public decimal NormalizedValue
{
get => this.normalizedValue;
set
{
this.normalizedValue = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ManiWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DatContext>
<StackPanel>
<!-- Input -->
<TextBox Text="{Binding UnitValue}" />
<ComboBox ItemsSource="{Binding Units}"
SelectedItem="{Binding SelectedUnit}" />
<TextBlock Text="{Binding NormalizedValue}" />
</StackPanel>
</Window>
Reusable solution
A reusable solution would be to create a custom control, which derives from TextBox and encapsulates the normalization logic and the control design.
The following custom control NormalizingNumericTextBox extends TextBox and converts two way from non-normalized value to normalized and back.
It is basically a TextBox aligned with a ComboBox as Unit selector.
It may not be perfect, but it is ready to use and it just took me about 10 minutes to merge the previous answer into this custom control.
NormalizingNumericTextBox supports any type of unit describing a numeric value.
Just bind the NormalizingNumericTextBox.Units property to collection of any kind of Unit implementation e.g. weight, length, currency, etc.
Bind to NormalizingNumericTextBox.NormalizedValue to get/set the normalized value. Setting this property will convert the value to the current NormalizingNumericTextBox.SelectedUnit.
Bind to NormalizingNumericTextBox.Text for the raw input value.
Ensure that the default Style (see below) is added to the ResourceDictionary inside /Themes/Generic.xaml. Customize this Style to customize appearance.
ManiWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DatContext>
<StackPanel>
<!-- Input -->
<NormalizingUnitTextBox NormalizedValue="{Binding NormalizedValue}"
Units="{Binding Units}"
Width="180" />
<!--
Test to show/manipulate current normalized value of the view model.
An entered normalized value will be converted back to the current NormalizingNumericTextBox.Unit -->
<TextBox Background="Red" Text="{Binding NormalizedUnitValue}"/>
</StackPanel>
</Window>
Unit.cs
public class Unit
{
public Unit(string name, decimal baseFactor)
{
this.Name = name;
this.BaseFactor = baseFactor;
}
#region Overrides of Object
/// <inheritdoc />
public override string ToString() => this.Name;
#endregion
public string Name { get; set; }
public decimal BaseFactor { get; set; }
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.Units = new List<Unit>()
{
new Unit("m²", 1),
new Unit("dm²", (decimal) (1/Math.Pow(10, 2))),
new Unit("cm²", (decimal) (1/Math.Pow(100, 2))),
new Unit("mm²", (decimal) (1/Math.Pow(1000, 2)))
};
}
public List<Unit> Units { get; set; }
private decimal normalizedValue;
public decimal NormalizedValue
{
get => this.normalizedValue;
set
{
this.normalizedValue = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
NormalizingNumericTextBox.cs
[TemplatePart(Name = "PART_UnitsItemsHost", Type = typeof(ItemsControl))]
public class NormalizingNumericTextBox : TextBox
{
public static readonly DependencyProperty UnitsProperty = DependencyProperty.Register(
"Units",
typeof(IEnumerable<Unit>),
typeof(NormalizingNumericTextBox),
new PropertyMetadata(default(IEnumerable<Unit>), NormalizingNumericTextBox.OnUnitsChanged));
public IEnumerable<Unit> Units
{
get => (IEnumerable<Unit>) GetValue(NormalizingNumericTextBox.UnitsProperty);
set => SetValue(NormalizingNumericTextBox.UnitsProperty, value);
}
public static readonly DependencyProperty SelectedUnitProperty = DependencyProperty.Register(
"SelectedUnit",
typeof(Unit),
typeof(NormalizingNumericTextBox),
new FrameworkPropertyMetadata(
default(Unit),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
NormalizingNumericTextBox.OnSelectedUnitChanged));
public Unit SelectedUnit
{
get => (Unit) GetValue(NormalizingNumericTextBox.SelectedUnitProperty);
set => SetValue(NormalizingNumericTextBox.SelectedUnitProperty, value);
}
public static readonly DependencyProperty NormalizedValueProperty = DependencyProperty.Register(
"NormalizedValue",
typeof(decimal),
typeof(NormalizingNumericTextBox),
new FrameworkPropertyMetadata(
default(decimal),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
NormalizingNumericTextBox.OnNormalizedValueChanged));
public decimal NormalizedValue
{
get => (decimal) GetValue(NormalizingNumericTextBox.NormalizedValueProperty);
set => SetValue(NormalizingNumericTextBox.NormalizedValueProperty, value);
}
private ItemsControl PART_UnitsItemsHost { get; set; }
private bool IsNormalizing { get; set; }
static NormalizingNumericTextBox()
{
FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(
typeof(NormalizingNumericTextBox),
new FrameworkPropertyMetadata(typeof(NormalizingNumericTextBox)));
}
public NormalizingNumericTextBox()
{
}
private static void OnNormalizedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var _this = d as NormalizingNumericTextBox;
_this.ConvertNormalizedValueToNumericText();
}
private static void OnSelectedUnitChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as NormalizingNumericTextBox).NormalizeNumericText();
}
private static void OnUnitsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var _this = d as NormalizingNumericTextBox;
_this.SelectedUnit = _this.Units.FirstOrDefault();
}
/// <inheritdoc />
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.PART_UnitsItemsHost = GetTemplateChild("PART_UnitsItemsHost") as ItemsControl;
if (this.PART_UnitsItemsHost == null)
{
throw new InvalidOperationException($"{nameof(this.PART_UnitsItemsHost)} not found in ControlTemplate");
}
this.PART_UnitsItemsHost.SetBinding(
Selector.SelectedItemProperty,
new Binding(nameof(this.SelectedUnit)) {Source = this});
this.PART_UnitsItemsHost.SetBinding(
ItemsControl.ItemsSourceProperty,
new Binding(nameof(this.Units)) {Source = this});
this.SelectedUnit = this.Units.FirstOrDefault();
}
#region Overrides of TextBoxBase
/// <inheritdoc />
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
if (this.IsNormalizing)
{
return;
}
NormalizeNumericText();
}
/// <inheritdoc />
protected override void OnTextInput(TextCompositionEventArgs e)
{
// Suppress non numeric characters
if (!decimal.TryParse(e.Text, NumberStyles.Number, CultureInfo.CurrentCulture, out decimal _))
{
e.Handled = true;
return;
}
base.OnTextInput(e);
}
#endregion Overrides of TextBoxBase
private void NormalizeNumericText()
{
this.IsNormalizing = true;
if (decimal.TryParse(this.Text, NumberStyles.Number, CultureInfo.CurrentCulture, out decimal numericValue))
{
this.NormalizedValue = numericValue * this.SelectedUnit.BaseFactor;
}
this.IsNormalizing = false;
}
private void ConvertNormalizedValueToNumericText()
{
this.IsNormalizing = true;
decimal value = this.NormalizedValue / this.SelectedUnit.BaseFactor;
this.Text = value.ToString(CultureInfo.CurrentCulture);
this.IsNormalizing = false;
}
}
Generic.xaml
<ResourceDictionary>
<Style TargetType="NormalizingNumericTextBox">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="DarkGray" />
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:NormalizingNumericTextBox">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ScrollViewer x:Name="PART_ContentHost" Grid.Column="0" Margin="0" />
<ComboBox x:Name="PART_UnitsItemsHost" Grid.Column="1" BorderThickness="0" HorizontalAlignment="Right" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
I have not much idea about your code impact but I would suggest you try below design which uses MVVM Pattern which removes tight coupling between UI and Backend.
I have separate out the things here
your XAML will have code like
<TextBox x:Name="unitTextbox"
Text="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
<ComboBox IsSynchronizedWithCurrentItem="True"
IsEditable="False"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedUnit}"
ItemsSource="{Binding AvailableUnits}">
</ComboBox>
Your ViewModel will be like
public class MainVm : Observable
{
#region Private Fields
private double _value;
private ObservableCollection<Unit> _availableUnits;
private Unit _selectedUnit;
private Unit _previouslySelected;
#endregion Private Fields
#region Public Constructors
public MainVm()
{
_availableUnits = new ObservableCollection<Unit>()
{
new Unit("mm²"),
new Unit("cm²"),
new Unit("dm²"),
new Unit("m²")
};
}
#endregion Public Constructors
#region Public Properties
public double Value
{
get
{
return _value;
}
set
{
if (_value != value)
{
_value = value;
OnPropertyChanged();
}
}
}
public Unit SelectedUnit
{
get { return _selectedUnit; }
set
{
_previouslySelected = _selectedUnit;
_selectedUnit = value;
// call to value conversion function
// convert cm² to mm² or anything
Value = UnitConvertor.Convert(_value, _previouslySelected.Name, _selectedUnit.Name);
OnPropertyChanged();
}
}
public ObservableCollection<Unit> AvailableUnits => _availableUnits;
#endregion Public Properties
}
My Observable class will be like
public class Observable : INotifyPropertyChanged
{
#region Public Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion Public Events
#region Protected Methods
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion Protected Methods
}
better to use an enum for units
I am new to WPF and learning how to bind the controls. I have created a UI which has different controls such as combobox and spinner (user defined control). Now I want to bind those controls such as combobox to a property of an object. Below is the sample code.
public class Parameter : INotifyPropertyChanged
{
protected decimal m_Code;
public decimal CODE
{
get { return m_Code; }
set
{
if (m_Code != value)
{
m_Code = value;
NotifyPropertyChanged("CODE");
}
}
}
protected decimal m_CurrentValue;
public decimal CURRENT_VALUE
{
get { return m_CurrentValue; }
set
{
if (m_CurrentValue != value)
{
m_CurrentValue = value;
NotifyPropertyChanged("CURRENT_VALUE");
}
}
}
protected Dictionary<int, string> m_ItemsDict;
public Dictionary<int, string> ItemsDict
{
get { return m_ItemsDict; }
set
{
//if (m_dict != value) Not comparing dictionaries. We should set if we get this request
{
m_ItemsDict = value;
NotifyPropertyChanged("ItemsDict");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
Below class is a singleton which has a dictionary where Key is an int and value is an object of parameter class.
public class ParameterDB: INotifyPropertyChanged
{
private static ParameterDB m_instance = new ParameterDB();
public static ParameterDB Instance
{
get { return m_instance; }
}
private Dictionary<int, Parameter> m_ParamDict;
private ParameterDB()
{
m_ParamDict = new Dictionary<int, Parameter>();
for(int i=0; i< 300; i++)
{
m_ParamDict.Add(i, new Parameter());
}
}
public Dictionary<int, Parameter> ParamDict
{
get
{
return m_ParamDict;
}
set
{
m_ParamDict = value;
NotifyPropertyChanged("ParamDict");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
Now I want to bind a control to, let's say, ParamDict[0].CURRENT_VALUE. I have created a custom combo box class.
public class MyComboBox : System.Windows.Controls.ComboBox
{
public readonly static DependencyProperty CodeProperty = DependencyProperty.Register(
"Code",
typeof(UInt32),
typeof(FlexSIMComboBox),
new PropertyMetadata(new UInt32()));
public UInt32 Code
{
get { return (UInt32)GetValue(CodeProperty); }
set
{
SetValue(CodeProperty, value);
}
}
public readonly static DependencyProperty ValueProperty = DependencyProperty.Register(
"Value",
typeof(decimal),
typeof(MyComboBox),
new PropertyMetadata(new decimal(0), ValueChangedCallback));
public decimal Value
{
get { return (decimal)GetValue(ValueProperty); }
set
{
SetValue(ValueProperty, value);
}
}
private static void ValueChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyComboBox myComboBox = d as MyComboBox;
if (myComboBox != null && e.NewValue is decimal)
{
// Check if new value is under the limit
decimal newValue = (decimal)e.NewValue;
myComboBox.SelectedValue = (int)newValue;
}
}
}
XAML file:
<local:MyComboBox
Grid.Column="1" x:Name="myCBCtrl" Margin="0,0,0,7" Width="180"
HorizontalAlignment="Left" VerticalAlignment="Bottom" IsReadOnly="true"
Code="{Binding CODE, Mode =TwoWay}"
Value="{Binding CURRENT_VALUE, Mode =TwoWay}"
ItemsSource="{Binding ItemsDict, Mode =TwoWay, NotifyOnTargetUpdated=True}"
SelectedValuePath="Key" DisplayMemberPath="Value" TargetUpdated="ComboBox_TargetUpdated"
StaysOpenOnEdit="True" SelectedIndex="0" SelectionChanged="ComboBox_SelectionChanged"/>
My question is how do I set DataContext for this control to an object which is in a dictionary?
For example to ParamDict[CODE]. CODE is also the key for the dictionary.
My intention is to build a dictionary and then bind my control to an item's property in that dictionary. So if the item's property is changed, it should reflect on the UI too. Any idea how to do that in XAML file.
I figured out how to do it.
<local:MyComboBox DataContext="{Binding Source={x:Static local:ParameterDB.Instance}}"
Grid.Column="1" x:Name="myCBCtrl" Margin="0,0,0,7" Width="180"
HorizontalAlignment="Left" VerticalAlignment="Bottom" IsReadOnly="true"
Code="{Binding Path=ParamDict[0].CODE, Mode =TwoWay}"
Value="{Binding Path=ParamDict[0].CURRENT_VALUE, Mode =TwoWay}"
ItemsSource="{Binding Path=ParamDict[0].ItemsDict, Mode =TwoWay, NotifyOnTargetUpdated=True}"
SelectedValuePath="Key" DisplayMemberPath="Value" TargetUpdated="ComboBox_TargetUpdated"
StaysOpenOnEdit="True" SelectedIndex="0" SelectionChanged="ComboBox_SelectionChanged"/>
Overview:
I've set up a property with INPC that invokes a page navigation in the view code behind from the MainViewModel. This property is bound to the SelectedItem property of a list view in the bound view.
The INPC implementation is inherited from the ViewModelBase class which is implemented as follows, https://gist.github.com/BrianJVarley/4a0890b678e037296aba
Issue:
When I select an item from the list view, the property SelectedCouncilItem setter doesn't trigger. This property is bound to the SelectedItem property of the list view.
Debugging Steps:
Checked binding names for SelectedItem in list view property, which was the same as the property name in the MainViewModel.
Ran the solution and checked for any binding errors in the output window, which there were none.
Placed a break point on the SelectedCouncilItem which doesn't get triggered when I select from the list view.
Checked the data context setup for the view which verified that the view is set to the data context of the MainViewModel.
Question:
Does anyone know what other steps I can take in debugging the issue, or what the issue might be?
Code:
MainPage - (List View)
<Grid x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<phone:LongListSelector x:Name="MainLongListSelector"
Margin="0,0,-12,0"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedCouncilItem}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock Style="{StaticResource PhoneTextExtraLargeStyle}"
Text="{Binding CouncilAcronym}"
TextWrapping="Wrap" />
<TextBlock Margin="12,-6,12,0"
Style="{StaticResource PhoneTextSubtleStyle}"
Text="{Binding CouncilFullName}"
TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
MainViewModel - (summary)
namespace ParkingTagPicker.ViewModels
{
public class MainViewModel : ViewModelBase
{
//Dependency Injection private instances
private INavigationCallback _navCallBack = null;
public MainViewModel()
{
this.Items = new ObservableCollection<ItemViewModel>();
}
/// <summary>
/// Creates and adds a few ItemViewModel objects into the Items collection.
/// </summary>
public void LoadCouncilNamesData()
{
this.Items.Add(new ItemViewModel() { ID = "6", CouncilAcronym = "WTC", CouncilFullName = "Wicklow Town Council"});
this.Items.Add(new ItemViewModel() { ID = "7", CouncilAcronym = "TS", CouncilFullName = "Tallaght Stadium" });
this.Items.Add(new ItemViewModel() { ID = "8", CouncilAcronym = "GS", CouncilFullName = "Greystones" });
this.IsDataLoaded = true;
}
public ObservableCollection<ItemViewModel> Items { get; private set; }
public bool IsDataLoaded { get; private set; }
private ItemViewModel _selectedCouncilItem;
public ItemViewModel SelectedCouncilItem
{
get
{
return this._selectedCouncilItem;
}
set
{
this.SetProperty(ref this._selectedCouncilItem, value, () => this._selectedCouncilItem);
if (_selectedCouncilItem != null)
{
_navCallBack.NavigateTo(_selectedCouncilItem.ID);
}
}
}
public INavigationCallback NavigationCallback
{
get { return _navCallBack; }
set { _navCallBack = value; }
}
}
}
ViewModelBase - (detailing INPC implementation)
namespace ParkingTagPicker.ViewModels
{
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var propertyChanged = this.PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
protected bool SetProperty<T>(ref T backingField, T Value, Expression<Func<T>> propertyExpression)
{
var changed = !EqualityComparer<T>.Default.Equals(backingField, Value);
if (changed)
{
backingField = Value;
this.RaisePropertyChanged(ExtractPropertyName(propertyExpression));
}
return changed;
}
private static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
{
var memberExp = propertyExpression.Body as MemberExpression;
if (memberExp == null)
{
throw new ArgumentException("Expression must be a MemberExpression.", "propertyExpression");
}
return memberExp.Member.Name;
}
}
}
There is an issue with the control. Please try using custom LongListSeletor
public class ExtendedLongListSelector : Microsoft.Phone.Controls.LongListSelector
{
public ExtendedLongListSelector()
{
SelectionChanged += LongListSelector_SelectionChanged;
}
void LongListSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItem = base.SelectedItem;
}
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(LongListSelector),
new PropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = (LongListSelector)d;
selector.SelectedItem = e.NewValue;
}
public new object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
}
and implement in replace it in XAML with the existing List.
xmlns:controls="clr-namespace:ProjectName.FolderName"
<controls:ExtendedLongListSelector x:Name="MainLongListSelector"
Margin="0,0,-12,0"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedCouncilItem}">
</controls:ExtendedLongListSelector>
With some help, I recently made binding collections in my custom control work. However, to my surprise I was told that to make the custom control property more flexible (that is possible to be bound with other parametrized collections), I needed to make the custom control's property to be of type IEnumerable<object> because of covariance and contravariance. However, this seems not to work for me
This is the control's view
<UserControl x:Class="BadaniaOperacyjne.Controls.Matrix"
mc:Ignorable="d" Name="CustomMatrix"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<!-- ... -->
<Grid Grid.Row="2" Grid.Column="1" Name="contentGrid">
<ListBox ItemsSource="{Binding ElementName=CustomMatrix, Path=ItemsList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
</UserControl>
and its code-behind is here
#region ItemsList Property
public static readonly DependencyProperty ItemsListProperty =
DependencyProperty.Register("ItemsList", typeof(IEnumerable<object>), typeof(Matrix), new PropertyMetadata(new PropertyChangedCallback(ItemsListChanged)));
public IEnumerable<object> ItemsList
{
get { return GetValue(ItemsListProperty) as IEnumerable<object>; }
set { SetValue(ItemsListProperty, value); }
}
private void ItemsListChanged(object value)
{
System.Diagnostics.Debug.WriteLine("matrix: items list changed " + value);
if (ItemsList != null)
{
//ItemsList.CollectionChanged += ItemsList_CollectionChanged;
System.Diagnostics.Debug.WriteLine("got " + string.Join(",", ItemsList.ToList()));
}
else
{
System.Diagnostics.Debug.WriteLine("got null");
}
}
void ItemsList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("matrix: current items list collection changed");
}
private static void ItemsListChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Matrix)d).ItemsListChanged(e.NewValue);
}
#endregion
and the Window that consumes the control is the following
<custom:Matrix x:Name="customMatrix" DockPanel.Dock="Top" Title="{Binding Title}" ItemsList="{Binding Items}"/>
with the code-behind like
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel()
{
Items = new ObservableCollection<int> { 1, 2, 3, 4, 5, 6};
Items.CollectionChanged += Items_CollectionChanged;
}
void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("problem manager: items list changed " + e.NewItems.Count);
}
public ObservableCollection<int> Items { get; private set; }
protected string title;
public string Title
{
get { return title; }
set
{
if (title != value)
{
title = value;
NotifyPropertyChanged("Title");
}
}
}
protected void NotifyPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public ViewModel VM { get; private set; }
// the window's constructor
private ProblemManager()
{
VM = new ViewModel();
DataContext = VM;
InitializeComponent();
VM.Title = "title";
}
private int i = 0;
private void btnAddRow_Click(object sender, RoutedEventArgs e)
{
//VM.Items.Add(++i);
VM.Items[2] = 112;
//customMatrix.ItemsList = new ObservableCollection<object> { 1, 2, 3 };
//customMatrix.ItemsList.Add(66);
//////
VM.Title = (++i).ToString();
}
When I change the DependencyProperty of the ItemsList control to ObservableCollection<int> or at least ObservableCollection<object>, it works fine.
Is it really possible? If so, then is the mistake I made?
Co-variance is allowed for IEnumerable but i just checked its only allowed for reference types and not for value types (e.g. int).
Your version will work if you bind with ObservableCollection<string> since string is reference type.
So what you can do is use IEnumerable (non-generic version) as return type of your DP like this so that it will work for value types as well:
public static readonly DependencyProperty ItemsListProperty =
DependencyProperty.Register("ItemsList", typeof(IEnumerable), typeof(Matrix),
new PropertyMetadata(new PropertyChangedCallback(ItemsListChanged)));
public IEnumerable ItemsList
{
get { return (IEnumerable)GetValue(ItemsListProperty); }
set { SetValue(ItemsListProperty, value); }
}
I'm building application using the MVVM pattern. After clicking on one of the elements I want to see this element's details. I wrote this:
XAML
<phone:LongListSelector ItemsSource="{Binding Data}"
Margin="0,0,0,158"
SelectedItem="{Binding SelectedItem}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button>
<!-- Command="{Binding ShowDetailsAction}"-->
<Button.Template>
<ControlTemplate>
<TextBlock Text="{Binding Text}"></TextBlock>
</ControlTemplate>
</Button.Template>
</Button>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
ViewModel:
public IEnumerable SelectedItem
{
get { return _itemsControl; }
set
{
if (_itemsControl == value)
return;
_itemsControl = value;
// Test
_mss.ErrorNotification("fd");
}
}
I tried also using a command, which didn't work, too.
This was the command part:
public ICommand ShowDetailsCommand { get; private set; }
public ViewModel()
{
_loadDataCommand = new DelegateCommand(LoadDataAction);
SaveChangesCommand = new DelegateCommand(SaveChangesAction);
ShowDetailsCommand = new DelegateCommand(ShowDetailsAction);
}
private void ShowDetailsAction(object p)
{
_mss.ErrorNotification("bla bla");
}
EDIT
ViewModel
private IEnumerable _itemsControl;
public IEnumerable Data
{
get
{
return _itemsControl;
}
set
{
_itemsControl = value;
RaisePropertyChanged("Data");
}
}
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Model
public string Text { get; set; }
public DateTimeOffset Data { get; set; }
EDIT2
private MobileServiceCollection<ModelAzure, ModelAzure> _items;
private readonly IMobileServiceTable<ModelAzure> _todoTable = App.MobileService.GetTable<ModelAzure>();
private async void RefreshTodoItems()
{
try
{
_items = await _todoTable.ToCollectionAsync();
}
catch (MobileServiceInvalidOperationException e)
{
_mss.ErrorNotification(e.ToString());
}
Data = _items;
}
Your Data property looks like
private MobileServiceCollection<ModelAzure, ModelAzure> _itemsControl;
public MobileServiceCollection<ModelAzure, ModelAzure> Data
{
get
{
return _itemsControl;
}
set
{
_itemsControl = value;
RaisePropertyChanged("Data");
}
}
Edited
It seems the SelectedItem property from LongListSelector cannot be bound in WP8.
What you can do is either :
Use the derived and fixed custom LongListSelector provided in the link above instead of the default one, which looks like :
public class LongListSelector : Microsoft.Phone.Controls.LongListSelector
{
public LongListSelector()
{
SelectionChanged += LongListSelector_SelectionChanged;
}
void LongListSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItem = base.SelectedItem;
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
"SelectedItem",
typeof(object),
typeof(LongListSelector),
new PropertyMetadata(null, OnSelectedItemChanged)
);
private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = (LongListSelector)d;
selector.SelectedItem = e.NewValue;
}
public new object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
}
Register the SelectionChanged event from LongListSelector and call your ViewModel by yourself inside the associated handler/callback :
in your view :
<phone:LongListSelector x:Name="YourLongListSelectorName"
ItemsSource="{Binding Data}"
Margin="0,0,0,158"
SelectionChanged="OnSelectedItemChanged">
in your code behind :
private void OnSelectedItemChanged(object sender, SelectionChangedEventArgs selectionChangedEventArgs e)
{
((YourViewModel)this.DataContext).NewSelectedItemMethodOrWhateverYouWant((ModelAzure)this.YourLongListSelectorName.SelectedItem);
//or
((YourViewModel)this.DataContext).SelectedItem = (ModelAzure)this.YourLongListSelectorName.SelectedItem;
}
Finally your Button command wasn't properly working, because when you use a DataTemplate, the ambiant DataContext is the item itself. Which means that it was looking for your Command into your Model instance, not into your ViewModel instance.
Hope this helps
In your ViewModel, you have:
public IEnumerable SelectedItem
{
get { return _itemsControl; }
set
{
if (_itemsControl == value)
return;
_itemsControl = value;
// Test
_mss.ErrorNotification("fd");
}
}
Why is your SelectItem an IEnumerable? Should it not be of type "Model"? Your list is bound to "Data" which should be ObservableList, not IEnumerable. It will provide it's own change notification, so you don't need to.
The list will set the SelectedItem when it gets selected, but if the type is wrong, it won't get set.
Greg