How to reference an UI element from ViewModel (WPF)? - c#

This might be simple for you guys but am just starting in WPF, and always my mind thinks in terms of Winforms, and am wrong all the time.
Anyway here is my situation. I have label in my view like below:
UserControl
<UserControl.Resources>
<Converters:BooleanToVisibilityConverter x:Key="visibilityConverter"></Converters:BooleanToVisibilityConverter>
<!-- Error Handling -->
<Converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<Converters:ErrorConverter x:Key="errorConverter"/>
<ControlTemplate x:Key="ErrorTemplate">
<Border BorderBrush="Red" BorderThickness="2">
<AdornedElementPlaceholder />
</Border>
</ControlTemplate>
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors), Converter={StaticResource errorConverter}}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="comboBoxInError" TargetType="{x:Type ComboBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors), Converter={StaticResource errorConverter}}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
Label
<Label Name="IsImageValid" Content="Image Created" Margin="0,7,-1,0" Style="{StaticResource LabelField}"
Grid.ColumnSpan="2" Grid.Row="15" Width="90" Height="28" Grid.RowSpan="2"
Grid.Column="1" IsEnabled="True"
Visibility="{Binding IsImageValid,Converter={StaticResource BooleanToVisibilityConverter}}" />
I am trying to call the this label in my view model but not sure how.
I have t following method in the viewmodel, planning to use the label to display some message based on the condition like below.
ViewModel
public class MetadataViewModel : NotificationObject, IMetadataViewModel
{
#region :: Properties ::
private IEventAggregator eventAggregator;
private IImageResizerService imageResizer;
private string headerInfo;
public string HeaderInfo
{
get
{
return headerInfo;
}
set
{
if (this.headerInfo != value)
{
this.headerInfo = value;
this.RaisePropertyChanged(() => this.HeaderInfo);
}
}
}
public ICommand SaveCommand
{
get;
private set;
}
public ICommand CloseCommand
{
get;
private set;
}
public ICommand DeleteCommand
{
get;
private set;
}
public ICommand SubmitCommand
{
get;
private set;
}
public ICommand UnSubmitCommand
{
get;
private set;
}
public ICommand LocationSearchCommand
{
get;
private set;
}
public ICommand SubjectSearchCommand
{
get;
private set;
}
public ICommand RemoveLocationCommand
{
get;
private set;
}
public ICommand RemoveSubjectCommand
{
get;
private set;
}
private StoryItem selectedStory;
public StoryItem SelectedStory
{
get
{
return this.selectedStory;
}
set
{
if (this.selectedStory != value)
{
this.selectedStory = value;
this.RaisePropertyChanged(() => this.SelectedStory);
// raise dependencies
this.RaisePropertyChanged(() => this.CanSave);
this.RaisePropertyChanged(() => this.CanUnSubmit);
this.RaisePropertyChanged(() => this.CanDelete);
}
}
}
public List<Program> ProgramList
{
get;
private set;
}
public List<Genre> GenreList
{
get;
private set;
}
public List<Copyright> CopyrightList
{
get;
private set;
}
public bool CanSave
{
get
{
bool canSave = false;
if (this.SelectedStory.IsLockAvailable)
{
if (!this.SelectedStory.Submitted)
{
canSave = true;
}
}
return canSave;
}
}
public bool CanDelete
{
get
{
bool canDelete = false;
if (this.SelectedStory.IsLockAvailable)
{
if (!this.SelectedStory.Submitted)
{
canDelete = true;
}
}
return canDelete;
}
}
public bool CanUnSubmit
{
get
{
bool canUnSubmit = false;
if (this.SelectedStory.IsLockAvailable)
{
if (this.SelectedStory.Submitted)
{
canUnSubmit = true;
}
}
return canUnSubmit;
}
}
#endregion
#region :: Contructor ::
[ImportingConstructor]
public MetadataViewModel(
IMetadataController metadataController,
IGatewayService gateway,
INavigationService navigator,
IImageResizerService imageResizer,
IEventAggregator eventAggregator
)
{
this.eventAggregator = eventAggregator;
this.imageResizer = imageResizer;
// populate drop-down lists
this.ProgramList = gateway.GetPrograms(true);
this.GenreList = gateway.GetGenres();
this.CopyrightList = gateway.GetCopyrights();
// add dummy values so the user can de-select
this.ProgramList.Add(new Program());
this.GenreList.Add(new Genre());
this.CopyrightList.Add(new Copyright());
// commands
this.SaveCommand = metadataController.SaveCommand;
this.CloseCommand = metadataController.CloseCommand;
this.DeleteCommand = metadataController.DeleteCommand;
this.SubmitCommand = metadataController.SubmitCommand;
this.UnSubmitCommand = metadataController.UnSubmitCommand;
this.LocationSearchCommand = new DelegateCommand<string>(this.LocationSearch);
this.SubjectSearchCommand = new DelegateCommand<string>(this.SubjectSearch);
this.RemoveLocationCommand = new DelegateCommand<Topic>(this.RemoveLocation);
this.RemoveSubjectCommand = new DelegateCommand<Topic>(this.RemoveSubject);
// events
this.eventAggregator.GetEvent<StorySelectedEvent>().Subscribe(OnStorySelected, ThreadOption.UIThread);
this.eventAggregator.GetEvent<AddLocationEvent>().Subscribe(OnAddLocation, ThreadOption.UIThread);
this.eventAggregator.GetEvent<AddSubjectEvent>().Subscribe(OnAddSubject, ThreadOption.UIThread);
this.eventAggregator.GetEvent<CommandCompletedEvent>().Subscribe(OnCommandCompleted, ThreadOption.UIThread);
this.eventAggregator.GetEvent<ImageResizeCompletedEvent>().Subscribe(OnImageResizeCompleted, ThreadOption.UIThread);
this.Initialize();
}
#endregion
private void OnStorySelected(StoryItem selectedStory)
{
if (this.selectedStory != null)
{
this.Initialize();
// override the initialized values
this.SelectedStory = selectedStory;
this.SelectedStory.HaveChanged = false;
this.HeaderInfo = "Edit";
}
}
public void OnAddLocation(Topic topic)
{
if (topic != null)
{
if (!this.SelectedStory.Locations.Contains(topic))
{
this.SelectedStory.Locations.Add(topic);
this.RaisePropertyChanged(() => this.SelectedStory.Locations);
}
}
}
public void OnAddSubject(Topic topic)
{
if (topic != null)
{
if (!this.SelectedStory.Subjects.Contains(topic))
{
this.SelectedStory.Subjects.Add(topic);
this.RaisePropertyChanged(() => this.SelectedStory.Subjects);
}
}
}
private void OnCommandCompleted(string commandType)
{
if (commandType == CommandTypes.MetadataEntry)
{
this.Initialize();
}
}
private void OnImageResizeCompleted(bool isSuccessful)
{
IsImageValid = false;
if (isSuccessful)
{
this.SelectedStory.KeyframeImages = true;
IsImageValid = true;
}
else
{
this.SelectedStory.KeyframeImages = false;
IsImageValid=false;
}
}
private void Initialize()
{
this.SelectedStory = new StoryItem();
this.HeaderInfo = "Create";
}
private void LocationSearch(object topicType)
{
this.eventAggregator.GetEvent<LocationSearchEvent>().Publish(null);
}
private void SubjectSearch(object topicType)
{
this.eventAggregator.GetEvent<SubjectSearchEvent>().Publish(null);
}
private void RemoveLocation(Topic selected)
{
if (selected != null)
{
// remove the primary too
if (this.SelectedStory.PrimaryLocation != null)
{
if (string.Equals(this.SelectedStory.PrimaryLocation.FullName, selected.FullName, StringComparison.InvariantCultureIgnoreCase))
{
this.SelectedStory.PrimaryLocation = new Topic();
}
}
bool isSuccessful = this.SelectedStory.Locations.Remove(selected);
if (isSuccessful)
{
this.RaisePropertyChanged(() => this.SelectedStory.Locations);
}
}
}
private void RemoveSubject(Topic selected)
{
if (selected != null)
{
// remove the primary too
if (this.SelectedStory.PrimarySubject != null)
{
if (string.Equals(this.SelectedStory.PrimarySubject.FullName, selected.FullName, StringComparison.InvariantCultureIgnoreCase))
{
this.SelectedStory.PrimarySubject = new Topic();
}
}
bool isSuccessful = this.SelectedStory.Subjects.Remove(selected);
if (isSuccessful)
{
this.RaisePropertyChanged(() => this.SelectedStory.Subjects);
}
}
}
}
private booly _isImageValid;
public bool IsImageValid
{
get
{
return _isImageValid;
}
set
{
_isImageValid = value;
this.RaisePropertyChanged(() => this.IsImageValid);
}
}
}
Honestly i don't know how view will understand the binding.

A standard approach is to have a boolean property like "IsImageValid" in your ViewModel...Then in your XAML, bind the Visibility property of your label to that property, with a BooleanToVisibilityConverter http://msdn.microsoft.com/en-us/library/system.windows.controls.booleantovisibilityconverter.aspx
<UserControl.Resources>
<BooleanToVisibilityConverter
x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources>
Then use it in one or more bindings like this:
<Label Visibility="{Binding IsImageValid,
Converter={StaticResource BooleanToVisibilityConverter}}"
......... />

please read this post first.
If you wanna display some text in your Label then your have to do the following steps:
add a Property to your Viewmodel
implement INotifyPropertyChanged in your Viewmodel and raise the event when ever the Property change
in your view set the DataContext to your Viewmodel instance
create a Binding in xaml to your property
thats all ;)

You don't have to see View from ViewModel, it is a basic principle behind MVVM pattern. View knows about VM, whereas VM does not know about View. Instead, you can do as #JeffN825 suggested, at least I'd recommend this as well.

Add the following to your user Control Property:
xmlns:VM="clr-namespace:<ProjectName>.ViewModels" //this place throws exception,what is <ProjectName> ?
For assigning the DataContext to the User Control use the following code if the DataContext is not already assigned:
<UserControl.DataContext> //where to add this part ?
<VM:MyViewModel>
</UserControl.DataContext>
Bind the Visibility of the label in the following manner:
Visibility="{Binding IsImageValid}" //this is done
Your ViewModel or ViewModelBase from which the VM is inherited should implement the INotifyPropertyChanged Interface:
namespace MyApp.ViewModels //this i have to do it at xaml.cs file or suppose to be in viewmodel ?
{
public class MyViewModel : INotifyPropertyChanged
{...
...
}
}
Declare the data member and Property in your VM like this:
private System.Windows.Visibility _isImageValid; //add this code in my viewmodel
public System.Windows.Visibility IsImageValid
{
get
{
return _isImageValid;
}
set
{
_isImageValid = value;
this.RaisePropertyChanged(() => this.IsImageValid);
}
}

Related

How to set ComboBoxItem property?

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"));
}
}
}
}

How to update/convert mumeric TextBox value when changing value's unit using a ComboBox? Value normalization based on current unit?

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

How to update a listView after execution of image trigger inside ViewModel in Xamarin?

I have a class Product
public class Product:ObservableObject, IKeyObject
{
[SQLite.PrimaryKey]
public string Id { get; set; } //this is cause i am using sqlite
//other properties
public bool IsFavorite { get; set; }
}
I also have a ViewModel ProductVm that inherits from BaseViewModel and this class BaseViewModel inherits from ObservableObject like
public abstract class BaseViewModel : ObservableObject
{
protected INavigation Navigation;
private bool _isBusy;
public bool IsBusy
{
get => _isBusy;
set { _isBusy = value; OnPropertyChanged(); }
}
protected BaseViewModel(INavigation navigation)
{
this.Navigation = navigation;
}
protected BaseViewModel()
{
}
}
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Now, ViewModel Product has an ObservableCollection<Product>
and a command
inside a page PageProduct, there is a binding of the view model so list can be updated depending on changes on a listView ProductResults
public class ProductVm : BaseViewModel
{
private ObservableCollection<Product> _productResults = new ObservableCollection<Product>();
public ObservableCollection<Product> ProductResults
{
get => _productResults ;
set { _productResults = value; OnPropertyChanged(); }
}
public ICommand FavoriteProductCommand
{
get;
set;
}
private void FavoriteProduct(Product product)
{
//here I tried traversing the ObservableCollection<Product>
// and then update the value with the product.IsFavorite value
//the item gets updated but the GUI does not
var item = _productResults.FirstOrDefault(i => i.Id == product.Id);
if (item != null)
{
item.IsFavorite = product.IsFavorite;
}
}
public ProductVm()
{
FavoriteProductCommand= new Command<Product>(FavoriteProduct);
}
Now inside PageProduct (named MyProductPage so I could use command and pass a product object in ViewModel) I have listView ProductFeed, and inside it has a grid that contains a button and an image, this image loads pincturea.png if product.IsFavorite is false, in other case it loads pinctureb.png I do this by using triggers like:
<ListView x:Name="ProductFeed"
HasUnevenRows="true"
ItemsSource="{Binding ProductResults}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid WidthRequest="30"
HeightRequest="30">
<Image BackgroundColor="Transparent"
IsOpaque="True"
x:Name="ImgFavorite"
>
<Image.Triggers>
<DataTrigger TargetType="Image"
Binding="{Binding IsFavorite}"
Value="false">
<Setter Property="Source"
Value="pincturea.png" />
</DataTrigger>
<DataTrigger TargetType="Image"
Binding="{Binding IsFavorite} "
Value="true">
<Setter Property="Source"
Value="pictureb.png" />
</DataTrigger>
</Image.Triggers>
</Image>
<Button
Command="{Binding Path=BindingContext.FavoriteProductCommand, Source={x:Reference MyProductPage}}" CommandParameter="{Binding .}"
BackgroundColor="Transparent"
BorderColor="Transparent"
Opacity="0.1">
</Button>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Problem
I want that after user clicks on displayed button (that shows either pincturea.png o pinctureb.png) it updates collection value and also the GUI
so it updates the shown image
Searched solutions
After searching I saw that everybody says that it has to do with a
INotifyPropertyChanged issue, but as far as I know I already did that part
doing this
public class ProductVm : BaseViewModel
and also declaring
private ObservableCollection<Product> _productResults = new ObservableCollection<Product>();
public ObservableCollection<Product> ProductResults
{
get => _productResults ;
set { _productResults = value; OnPropertyChanged(); }
}
Other solutions involve:
listview.isRefreshing = true;
//update observableCollection
listview.isRefreshing = false;
but as I am working on ViewModel I do not have access to listview there..
What am I missing, so in my method
private void FavoriteProduct(Product product)
I can update GUI too?
You are not calling property changed in your model
Try this
public class Product:ObservableObject, IKeyObject
{
[SQLite.PrimaryKey]
public string Id { get; set; } //this is cause i am using sqlite
//other properties
private bool _isFavorite;
public bool IsFavorite
{
get => _ isFavorite;
set { _ isFavorite = value; OnPropertyChanged(); }
}
}

Why invoking command when `TreeViewItem` is expanded doesn't work?

I'm trying to invoke command when TreeViewItem is expanded as explained here, but for some reason it doesn't work. I think it's because of HierarchicalDataTemplate, but I don't know why.
Does any one have an idea what's the problem?
XAML
<Window x:Class="MyProject.MainWindow"
...
xmlns:local="clr-namespace:MyProject"
xmlns:bindTreeViewExpand="clr-namespace:MyProject"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TreeView ItemsSource="{Binding RootFolders}">
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="bindTreeViewExpand:Behaviours.ExpandingBehaviour" Value="{Binding ExpandingCommand}"/>
</Style>
</TreeView.Resources>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" DataType="{x:Type local:DriveFolder}">
<TreeViewItem Header="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
</Window>
BEHAVIOURS
namespace GooglePhotoPermissions
{
public static class Behaviours
{
#region ExpandingBehaviour (Attached DependencyProperty)
public static readonly DependencyProperty ExpandingBehaviourProperty =
DependencyProperty.RegisterAttached("ExpandingBehaviour", typeof(ICommand), typeof(Behaviours),
new PropertyMetadata(OnExpandingBehaviourChanged));
public static void SetExpandingBehaviour(DependencyObject o, ICommand value)
{
o.SetValue(ExpandingBehaviourProperty, value);
}
public static ICommand GetExpandingBehaviour(DependencyObject o)
{
return (ICommand)o.GetValue(ExpandingBehaviourProperty);
}
private static void OnExpandingBehaviourChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TreeViewItem tvi = d as TreeViewItem;
if (tvi != null)
{
ICommand ic = e.NewValue as ICommand;
if (ic != null)
{
tvi.Expanded += (s, a) =>
{
if (ic.CanExecute(a))
{
ic.Execute(a);
}
a.Handled = true;
};
}
}
}
#endregion
}
}
ViewModel
namespace MyProject
{
public class DriveFile
{
public string Name { get; set; }
public string Id { get; set; }
public bool IsFolder { get; protected set; }
public DriveFile()
{
IsFolder = false;
}
}
public class DriveFolder : DriveFile
{
public List<DriveFile> Children { get; set; }
public DriveFolder()
{
IsFolder = true;
Children = new List<DriveFile>();
}
}
public class DriveViewModel
{
public IList<DriveFolder> RootFolders
{
get
{
return GetRootFolders();
}
}
private ICommand _expandingCommand;
public ICommand ExpandingCommand
{
get
{
if (_expandingCommand == null)
{
_expandingCommand = new RelayCommand(Foo);
}
return _expandingCommand;
}
}
private DriveService _driveService;
private IList<DriveFolder> GetRootFolders()
{
...
}
}
}
a
Your binding is wrong.
You define a binding in a style that applies to each TreeViewItem. In this binding, the source is the DataContext of each TreeViewItem itself. That would be a DriveFolder or a DriveFile object.
Of course, these objects have no ExpandingCommand property, so your binding just fails.
Change your binding in such a manner that the DataContext of the TreeView is used as source (to access your view model and its command). You can use ElementName or RelativeSource, e.g. like this:
<Setter Property="bindTreeViewExpand:Behaviours.ExpandingBehaviour"
Value="{Binding DataContext.ExpandingCommand, RelativeSource={RelativeSource AncestorType=TreeView}}"/>

How to make "CanSave" property to Enable/disable binded SaveButton to RelayCommand?

I working on WPF application using MVVM pattern.
the question is :
How to Check wether the Textboxes content are valid or not and by the way make a Save Button Disable until all textboxes fill by valid data.
I googled more and more but in all scenario the answer was implementing IDataerrorInfo on the model but i using entity frameworks entity as model so can not do the implementation.
please give me some advice or links .
thanks in advance.
Below my project description:
the XAML:
<TextBox Name="txtName" Height="23" HorizontalAlignment="Left" Margin="56,42,0,0" VerticalAlignment="Top" Width="120" >
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
<Binding Path="CurrentItem.Name" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<vm:NotNullValidationRule ValidatesOnTargetUpdated="True"/>
</Binding.ValidationRules>
</Binding>
</TextBox>
and the PersonViweModel.cs
public class PersonViewModel : WorkspaceViewModel
{
SDPS.Business.Base.BaseBusiness<Person> bz = new Business.Base.BaseBusiness<Person>();
#region Constructor
public PersonViewModel(string displayTitle)
{
Items = bz.GetAll();
DisplayName = displayTitle;
NewCommand = new RelayCommand(p => NewItem());
SaveCommand = new RelayCommand(p => SaveItem());
UpdateCommand = new RelayCommand(p => UpdateItem(), p => CanUpdateOrDelete);
DeleteCommand = new RelayCommand(p => DeleteItem(), p => CanUpdateOrDelete);
}
#endregion
#region Methods
private void NewItem()
{
CurrentItem = new Person();
}
private void SaveItem()
{
bz.Add(CurrentItem);
Items.Add(CurrentItem);
}
private void DeleteItem()
{
bz.Delete(CurrentItem);
Items.Remove(CurrentItem);
OnPropertyChanged("Items");
OnPropertyChanged("CurrentItem");
}
public void UpdateItem()
{
bz.Update(CurrentItem);
OnPropertyChanged("Items");
}
#endregion
#region Properties
public Person CurrentItem
{
get { return _CurrentItem; }
set
{
_CurrentItem = value;
OnPropertyChanged("CurrentItem");
}
}
public ObservableCollection<Person> Items
{
get { return _items; }
set
{
_items = value;
OnPropertyChanged("Items");
}
}
public bool CanUpdateOrDelete { get { return CurrentItem != null; } }
#endregion
#region Variables
private ObservableCollection<Person> _items;
private Person _CurrentItem;
#endregion
}
and the ValidationRule Class as below:
public class NotNullValidationRule : ValidationRule
{
public virtual bool HasError
{
get
{
return null;
}
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var result = new ValidationResult(true, null);
if(value!=null)
if (string.IsNullOrWhiteSpace( value.ToString()) )
{
result = new ValidationResult(false, "null string not allowed");
}
return result;
}
}

Categories

Resources