How can I bind class fields to elements inside custom templates ? (WPF) - c#

How can I bind class fields to elements inside a custom ListBoxItem template??
This sample code is lacking some aspects like class definition, ListBox name etc., I just struggle with the binding process.
<ListBox>
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- how to bind class fields to these elements -->
<StackPanel Orientation="Horizontal">
<Label></Label>
<Image></Image>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<!-- adding items dynamically -->
</ListBox>
C# code should look like:
ListBoxItem listBoxItem = new ListBoxItem();
listBoxItem.Content = new MyClass(){ Name="MyName", Image="ImagePath"}
... append to ListBox ...

When developing in WPF MvvM is being encouraged to promote separation of concerns. To do so one would implement a View Model with Properties that then would be bound to the view. When you want the UI (view) to be aware of the changes to the data that View Model provides one has to implement the INotifyPropertyChanged interface, like so:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Threading;
namespace ViewModels
{
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public BaseViewModel()
{
//ctor
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
UIThread( () =>
{
//make sure the event is raised on the main thread
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
});
}
Dispatcher _dispacther;
protected void UIThread(Action action)
{
if (_dispacther == null)
{
_dispacther = Dispatcher.CurrentDispatcher;
}
_dispacther.Invoke(action);
}
}
}
Your Hello class would derive from the BaseViewModel and provide OnPropertyChanged(); in the property like this:
private string name;
public string Name
{
get { return name; }
set { name= value; OnPropertyChanged(); }
}
Now when you change a name of the selected Item in the ListBox it will be reflected in the UI. Try it!
This implementation ensures that the event is raised on main thread, so you don't need to do it in when invoking the event. Also [CallerMemberName] will populate the name of the property that you invoke!

Figured it out. C# class:
public class Hello
{
public string Name { get; set; }
public string Description { get; set; }
public string ImagePath { get; set; }
}
C# dynamic adding:
ListBoxItem lbi = new ListBoxItem();
lbi.Content = new Hello() { Name = "hello man", Description="I am superman.", ImagePath="Images/myimage.png"};
menu.Items.Add(lbi);
XAML:
<ListBox Name="menu" ItemsSource="{Binding}">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label FontWeight="Bold" Content="{Binding Name}"></Label>
<Label Content="{Binding Description}"></Label>
<Image Source="{Binding ImagePath}" Width="30" Height="30"></Image>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
</ListBox>

Related

Raising Property Changed for property outside of the ViewModel

Using MvvmCross, fwiw I have a ViewModel with several properties created primarily for ease XAML binding purposes. For example:
public int HomeScore
{
get { return Contest.Team[HomeID].Score; }
}
HomeScore is bound to a TextBlock in the XAML view.
Contest is a singleton class that contains a dictionary Team of two teams, HomeID representing one of the Keys. The value is a class of TeamStats, that contains a property integer Score.
The current dilemma / challenge is when another method updates the Score, how should that notification get passed on to the ViewModel and subsequently the View to show the updated score in the display.
I've played around with the MvvmCross SetProperty and RaisePropertyChanged at various levels but all to no avail.
If the Team's "Score" property itself publishes/raises PropertyChanged, you need to listen to it and on any change raise PropertyChanged for "HomeScore".
Contest.Team[HomeID].PropertyChanged += PropagateHomeScore;
private void PropagateHomeScore (object sender, PropertyChangedEventArgs args) {
if (e.PropertyName == "Score") {
RaisePropertyChanged (nameof(HomeScore))
}
}
By the way, if you discard the convenience wrapper "HomeScore" and put the property path directly in XAML, you don't have to do anything.
WPF would bind the complete path including the change listeners automagically. Afaik it can handle the dictionary indexer.
XAML
<TextBlock Text="{Binding Contest.Team[HomeID].Score}" />
(HomeID should likely be replaced by its actual value).
**
Update:
Demo for Binding to a dictionary of a static class**
XAML Window1.xaml
<Window x:Class="WpfApp1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
Title=""
Width="700"
Height="220">
<StackPanel HorizontalAlignment="Center">
<StackPanel.Resources>
<Style x:Key="Style1" TargetType="Control">
<Setter Property="FontSize" Value="20" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Padding" Value="20" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</StackPanel.Resources>
<Label Style="{StaticResource Style1}">Dictionary-Binding + INotifyPropertyChanged Demo</Label>
<StackPanel HorizontalAlignment="Center"
Orientation="Horizontal">
<Button Margin="10"
Click="ButtonBase_OnClick"
Content="Increment:"
Style="{StaticResource Style1}" />
<TextBox Foreground="Magenta"
IsReadOnly="True"
Style="{StaticResource Style1}"
Text="{Binding Source={x:Static local:Contest.Team}, Path=[1].Score, Mode=OneWay}" />
</StackPanel>
</StackPanel>
</Window>
CS: Window1.xaml.cs
Code behind + VM
namespace WpfApp1 {
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
public partial class Window1 {
public Window1() => InitializeComponent();
private void ButtonBase_OnClick(object sender, RoutedEventArgs e) => Contest.Team[1].Score++;
}
public static class Contest {
public static Dictionary<int, ScoreObject> Team { get; } = new() {
{ 1, new ScoreObject { Score = 10 } },
{ 2, new ScoreObject { Score = 20 } },
{ 3, new ScoreObject { Score = 30 } },
{ 4, new ScoreObject { Score = 40 } },
};
}
public class ScoreObject : INotifyPropertyChanged {
private int _score;
public int Score {
get => _score;
set {
if (_score != value) {
_score = value;
OnPropertyChanged(nameof(Score));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Reading the "IsExpanded" property from the ItemSource of a TreeViewItem

So I want the IsExpanded Property of the TreeView Item so be reflected in the datacontext.
<TreeView x:Name="TreeViewMonitorEvents" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.Column="0" Grid.Row="1" Grid.RowSpan="5"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Page}, Path=MonitorEventCatagories}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type tree:TreeGroup}" ItemsSource="{Binding Members}" >
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" MouseMove="DragDrop_MouseMove_TreeGroup">
<CheckBox Name="CheckboxTreeGroup" IsChecked="{Binding Path=(tree:TreeItemHelper.IsChecked), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Template="{DynamicResource MonitoringUICheckBox}" Style="{StaticResource MonitoringUICheckBoxStyling}"
MouseMove="DragDrop_MouseMove_TreeGroup" Checked="CheckboxTreeGroup_Checked" Unchecked="CheckboxTreeGroup_Unchecked">
</CheckBox>
<TextBlock Text="{Binding Name}" Style="{StaticResource MonitorUIText}" MouseMove="DragDrop_MouseMove_TreeGroup"/>
</StackPanel>
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" >
<Setter Property="IsExpanded" Value="{Binding IsExpanded,Mode=TwoWay}" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
I.e the TreeGroup Class. See Treegroup here:
namespace RTX64MonitorUtility.TreeView
{
//Data class. Holds information in the Datacontext section of the UI Elements it is attached to.
public class TreeGroup : DependencyObject, IParent<object>
{
public string Name { get; set; }
public List<TreeMonitoringEvent> Members { get; set; }
public IEnumerable<object> GetChildren()
{
return Members;
}
public bool IsExpanded { get; set; } = false;
}
}
Here is the List it's drawing from:
namespace RTX64MonitorUtility.Pages
{
/// <summary>
/// Interaction logic for EventsAndTriggers.xaml
/// </summary>
public partial class EventsAndTriggers : Page
{
public ObservableCollection<TreeGroup> MonitorEventCatagories { get; set; }
...
}
}
My primary goal here is that if the TreeGroup Item is not expanded then the chidren's Checked and Unchecked events do not get triggered so I need to do the required actions for them. If I can find out a way of reading the IsExpanded value from those events that would also be a solution.
You usually bind the item container's properties like TreeViewItem.IsExpanded or ListBoxItem.IsSelected etc. to your data model by using a Style that targets the item container. The DataContext of the item container is the data model:
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Next, implement the data model properly. The properties that are the source of the data binding must be implemented as dependency properties (for dependency objects) or raise INotifyPropertyChanged.PropertyChanged event.
Since DependencyObject extends DispatcherObject, any class that extends DependencyObject is thread affine: in WPF, a DispatcherObject can only be accessed by the Dispatcher it is associated with.
For this reason you usually prefer INotifyPropertyChanged on data models.
DependencyObject is for UI objects that are bound to the Dispatcher thread by definition.
// Let the model implement the INotifyPropertyChanged interface
public class TreeGroup : INotifyPropertyChanged, IParent<object>
{
// Follow this pattern for all properties that will change
// and are source of a data binding.
private bool isExpanded;
public IsExpanded
{
get => this.isExpanded;
set;
{
this.isExpanded = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
See Microsoft Docs: Data binding overview (WPF .NET)

how to get selected radiobutton in viewmodel from stackpanel [duplicate]

EDIT: Problem was fixed in .NET 4.0.
I have been trying to bind a group of radio buttons to a view model using the IsChecked button. After reviewing other posts, it appears that the IsChecked property simply doesn't work. I have put together a short demo that reproduces the problem, which I have included below.
Here is my question: Is there a straightforward and reliable way to bind radio buttons using MVVM? Thanks.
Additional information: The IsChecked property doesn't work for two reasons:
When a button is selected, the IsChecked properties of other buttons in the group don't get set to false.
When a button is selected, its own IsChecked property does not get set after the first time the button is selected. I am guessing that the binding is getting trashed by WPF on the first click.
Demo project: Here is the code and markup for a simple demo that reproduces the problem. Create a WPF project and replace the markup in Window1.xaml with the following:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300" Loaded="Window_Loaded">
<StackPanel>
<RadioButton Content="Button A" IsChecked="{Binding Path=ButtonAIsChecked, Mode=TwoWay}" />
<RadioButton Content="Button B" IsChecked="{Binding Path=ButtonBIsChecked, Mode=TwoWay}" />
</StackPanel>
</Window>
Replace the code in Window1.xaml.cs with the following code (a hack), which sets the view model:
using System.Windows;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new Window1ViewModel();
}
}
}
Now add the following code to the project as Window1ViewModel.cs:
using System.Windows;
namespace WpfApplication1
{
public class Window1ViewModel
{
private bool p_ButtonAIsChecked;
/// <summary>
/// Summary
/// </summary>
public bool ButtonAIsChecked
{
get { return p_ButtonAIsChecked; }
set
{
p_ButtonAIsChecked = value;
MessageBox.Show(string.Format("Button A is checked: {0}", value));
}
}
private bool p_ButtonBIsChecked;
/// <summary>
/// Summary
/// </summary>
public bool ButtonBIsChecked
{
get { return p_ButtonBIsChecked; }
set
{
p_ButtonBIsChecked = value;
MessageBox.Show(string.Format("Button B is checked: {0}", value));
}
}
}
}
To reproduce the problem, run the app and click Button A. A message box will appear, saying that Button A's IsChecked property has been set to true. Now select Button B. Another message box will appear, saying that Button B's IsChecked property has been set to true, but there is no message box indicating that Button A's IsChecked property has been set to false--the property hasn't been changed.
Now click Button A again. The button will be selected in the window, but no message box will appear--the IsChecked property has not been changed. Finally, click on Button B again--same result. The IsChecked property is not updated at all for either button after the button is first clicked.
If you start with Jason's suggestion then the problem becomes a single bound selection from a list which translates very nicely to a ListBox. At that point it's trivial to apply styling to a ListBox control so that it shows up as a RadioButton list.
<ListBox ItemsSource="{Binding ...}" SelectedItem="{Binding ...}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<RadioButton Content="{TemplateBinding Content}"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Looks like they fixed binding to the IsChecked property in .NET 4. A project that was broken in VS2008 works in VS2010.
For the benefit of anyone researching this question down the road, here is the solution I ultimately implemented. It builds on John Bowen's answer, which I selected as the best solution to the problem.
First, I created a style for a transparent list box containing radio buttons as items. Then, I created the buttons to go in the list box--my buttons are fixed, rather than read into the app as data, so I hard-coded them into the markup.
I use an enum called ListButtons in the view model to represent the buttons in the list box, and I use each button's Tag property to pass a string value of the enum value to use for that button. The ListBox.SelectedValuePath property allows me to specify the Tag property as the source for the selected value, which I bind to the view model using the SelectedValue property. I thought I would need a value converter to convert between the string and its enum value, but WPF's built-in converters handled the conversion without problem.
Here is the complete markup for Window1.xaml:
<Window x:Class="RadioButtonMvvmDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<!-- Resources -->
<Window.Resources>
<Style x:Key="RadioButtonList" TargetType="{x:Type ListBox}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="5" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border BorderThickness="0" Background="Transparent">
<RadioButton
Focusable="False"
IsHitTestVisible="False"
IsChecked="{TemplateBinding IsSelected}">
<ContentPresenter />
</RadioButton>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<Border BorderThickness="0" Padding="0" BorderBrush="Transparent" Background="Transparent" Name="Bd" SnapsToDevicePixels="True">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<!-- Layout -->
<Grid>
<!-- Note that we use SelectedValue, instead of SelectedItem. This allows us
to specify the property to take the value from, using SelectedValuePath. -->
<ListBox Style="{StaticResource RadioButtonList}" SelectedValuePath="Tag" SelectedValue="{Binding Path=SelectedButton}">
<ListBoxItem Tag="ButtonA">Button A</ListBoxItem>
<ListBoxItem Tag="ButtonB">Button B</ListBoxItem>
</ListBox>
</Grid>
</Window>
The view model has a single property, SelectedButton, which uses a ListButtons enum to show which button is selected. The property calls an event in the base class I use for view models, which raises the PropertyChanged event:
namespace RadioButtonMvvmDemo
{
public enum ListButtons {ButtonA, ButtonB}
public class Window1ViewModel : ViewModelBase
{
private ListButtons p_SelectedButton;
public Window1ViewModel()
{
SelectedButton = ListButtons.ButtonB;
}
/// <summary>
/// The button selected by the user.
/// </summary>
public ListButtons SelectedButton
{
get { return p_SelectedButton; }
set
{
p_SelectedButton = value;
base.RaisePropertyChangedEvent("SelectedButton");
}
}
}
}
In my production app, the SelectedButton setter will call a service class method that will take the action required when a button is selected.
And to be complete, here is the base class:
using System.ComponentModel;
namespace RadioButtonMvvmDemo
{
public abstract class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Protected Methods
/// <summary>
/// Raises the PropertyChanged event.
/// </summary>
/// <param name="propertyName">The name of the changed property.</param>
protected void RaisePropertyChangedEvent(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, e);
}
}
#endregion
}
}
Hope that helps!
One solution is to update the ViewModel for the radio buttons in the setter of the properties. When Button A is set to True, set Button B to false.
Another important factor when binding to an object in the DataContext is that the object should implement INotifyPropertyChanged. When any bound property changes, the event should be fired and include the name of the changed property. (Null check omitted in the sample for brevity.)
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool _ButtonAChecked = true;
public bool ButtonAChecked
{
get { return _ButtonAChecked; }
set
{
_ButtonAChecked = value;
PropertyChanged(this, new PropertyChangedEventArgs("ButtonAChecked"));
if (value) ButtonBChecked = false;
}
}
protected bool _ButtonBChecked;
public bool ButtonBChecked
{
get { return _ButtonBChecked; }
set
{
_ButtonBChecked = value;
PropertyChanged(this, new PropertyChangedEventArgs("ButtonBChecked"));
if (value) ButtonAChecked = false;
}
}
}
Edit:
The issue is that when first clicking on Button B the IsChecked value changes and the binding feeds through, but Button A does not feed through its unchecked state to the ButtonAChecked property. By manually updating in code the ButtonAChecked property setter will get called the next time Button A is clicked.
Here is another way you can do it
VIEW:
<StackPanel Margin="90,328,965,389" Orientation="Horizontal">
<RadioButton Content="Mr" Command="{Binding TitleCommand, Mode=TwoWay}" CommandParameter="{Binding Content, RelativeSource={RelativeSource Mode=Self}, Mode=TwoWay}" GroupName="Title"/>
<RadioButton Content="Mrs" Command="{Binding TitleCommand, Mode=TwoWay}" CommandParameter="{Binding Content, RelativeSource={RelativeSource Mode=Self}, Mode=TwoWay}" GroupName="Title"/>
<RadioButton Content="Ms" Command="{Binding TitleCommand, Mode=TwoWay}" CommandParameter="{Binding Content, RelativeSource={RelativeSource Mode=Self}, Mode=TwoWay}" GroupName="Title"/>
<RadioButton Content="Other" Command="{Binding TitleCommand, Mode=TwoWay}" CommandParameter="{Binding Content, RelativeSource={RelativeSource Mode=Self}}" GroupName="Title"/>
<TextBlock Text="{Binding SelectedTitle, Mode=TwoWay}"/>
</StackPanel>
ViewModel:
private string selectedTitle;
public string SelectedTitle
{
get { return selectedTitle; }
set
{
SetProperty(ref selectedTitle, value);
}
}
public RelayCommand TitleCommand
{
get
{
return new RelayCommand((p) =>
{
selectedTitle = (string)p;
});
}
}
Not sure about any IsChecked bugs, one possible refactor you could make to your viewmodel:the view has a number of mutually exclusive states represented by a series of RadioButtons, only one of which at any given time can be selected. In the view model, just have 1 property (e.g. an enum) which represents the possible states: stateA, stateB, etc That way you wouldn't need all the individual ButtonAIsChecked, etc
A small extension to John Bowen's answer: It doesn't work when the values don't implement ToString(). What you need instead of setting the Content of the RadioButton to a TemplateBinding, just put a ContentPresenter in it, like this:
<ListBox ItemsSource="{Binding ...}" SelectedItem="{Binding ...}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<RadioButton IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected}">
<ContentPresenter/>
</RadioButton>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
This way you can additionally use DisplayMemberPath or an ItemTemplate as appropriate. The RadioButton just "wraps" the items, providing the selection.
I know this is an old question and the original issue was resolved in .NET 4. and in all honesty this is slightly off topic.
In most cases where I've wanted to use RadioButtons in MVVM it's to select between elements of an enum, this requires binding a bool property in the VM space to each button and using them to set an overall enum property that reflects the actual selection, this gets very tedious very quick. So I came up with a solution that is re-usable and very easy to implement, and does not require ValueConverters.
The View is pretty much the same, but once you have your enum in place the VM side can be done with a single property.
MainWindowVM
using System.ComponentModel;
namespace EnumSelectorTest
{
public class MainWindowVM : INotifyPropertyChanged
{
public EnumSelectorVM Selector { get; set; }
private string _colorName;
public string ColorName
{
get { return _colorName; }
set
{
if (_colorName == value) return;
_colorName = value;
RaisePropertyChanged("ColorName");
}
}
public MainWindowVM()
{
Selector = new EnumSelectorVM
(
typeof(MyColors),
MyColors.Red,
false,
val => ColorName = "The color is " + ((MyColors)val).ToString()
);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The class that does all the work inherits from DynamicObject. Viewed from the outside it creates a bool property for each element in the enum prefixed with 'Is', 'IsRed', 'IsBlue' etc. that can be bound to from XAML. Along with a Value property that holds the actual enum value.
public enum MyColors
{
Red,
Magenta,
Green,
Cyan,
Blue,
Yellow
}
EnumSelectorVM
using System;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
namespace EnumSelectorTest
{
public class EnumSelectorVM : DynamicObject, INotifyPropertyChanged
{
//------------------------------------------------------------------------------------------------------------------------------------------
#region Fields
private readonly Action<object> _action;
private readonly Type _enumType;
private readonly string[] _enumNames;
private readonly bool _notifyAll;
#endregion Fields
//------------------------------------------------------------------------------------------------------------------------------------------
#region Properties
private object _value;
public object Value
{
get { return _value; }
set
{
if (_value == value) return;
_value = value;
RaisePropertyChanged("Value");
_action?.Invoke(_value);
}
}
#endregion Properties
//------------------------------------------------------------------------------------------------------------------------------------------
#region Constructor
public EnumSelectorVM(Type enumType, object initialValue, bool notifyAll = false, Action<object> action = null)
{
if (!enumType.IsEnum)
throw new ArgumentException("enumType must be of Type: Enum");
_enumType = enumType;
_enumNames = enumType.GetEnumNames();
_notifyAll = notifyAll;
_action = action;
//do last so notification fires and action is executed
Value = initialValue;
}
#endregion Constructor
//------------------------------------------------------------------------------------------------------------------------------------------
#region Methods
//---------------------------------------------------------------------
#region Public Methods
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string elementName;
if (!TryGetEnumElemntName(binder.Name, out elementName))
{
result = null;
return false;
}
try
{
result = Value.Equals(Enum.Parse(_enumType, elementName));
}
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException || ex is OverflowException)
{
result = null;
return false;
}
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object newValue)
{
if (!(newValue is bool))
return false;
string elementName;
if (!TryGetEnumElemntName(binder.Name, out elementName))
return false;
try
{
if((bool) newValue)
Value = Enum.Parse(_enumType, elementName);
}
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException || ex is OverflowException)
{
return false;
}
if (_notifyAll)
foreach (var name in _enumNames)
RaisePropertyChanged("Is" + name);
else
RaisePropertyChanged("Is" + elementName);
return true;
}
#endregion Public Methods
//---------------------------------------------------------------------
#region Private Methods
private bool TryGetEnumElemntName(string bindingName, out string elementName)
{
elementName = "";
if (bindingName.IndexOf("Is", StringComparison.Ordinal) != 0)
return false;
var name = bindingName.Remove(0, 2); // remove first 2 chars "Is"
if (!_enumNames.Contains(name))
return false;
elementName = name;
return true;
}
#endregion Private Methods
#endregion Methods
//------------------------------------------------------------------------------------------------------------------------------------------
#region Events
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion Events
}
}
To respond to changes you can either subscribe to the NotifyPropertyChanged event or pass an anonymous method to the constructor as done above.
And finally the MainWindow.xaml
<Window x:Class="EnumSelectorTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<RadioButton IsChecked="{Binding Selector.IsRed}">Red</RadioButton>
<RadioButton IsChecked="{Binding Selector.IsMagenta}">Magenta</RadioButton>
<RadioButton IsChecked="{Binding Selector.IsBlue}">Blue</RadioButton>
<RadioButton IsChecked="{Binding Selector.IsCyan}">Cyan</RadioButton>
<RadioButton IsChecked="{Binding Selector.IsGreen}">Green</RadioButton>
<RadioButton IsChecked="{Binding Selector.IsYellow}">Yellow</RadioButton>
<TextBlock Text="{Binding ColorName}"/>
</StackPanel>
</Grid>
</Window>
Hope someone else finds this useful, 'cause I reckon this ones going in my toolbox.
You have to add the Group Name for the Radio button
<StackPanel>
<RadioButton Content="Button A" IsChecked="{Binding Path=ButtonAIsChecked, Mode=TwoWay}" GroupName="groupName" />
<RadioButton Content="Button B" IsChecked="{Binding Path=ButtonBIsChecked, Mode=TwoWay}" GroupName="groupName" />
</StackPanel>
I have a very similar problem in VS2015 and .NET 4.5.1
XAML:
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="6" Rows="1"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate >
<RadioButton GroupName="callGroup" Style="{StaticResource itemListViewToggle}" Click="calls_ItemClick" Margin="1" IsChecked="{Binding Path=Selected,Mode=TwoWay}" Unchecked="callGroup_Checked" Checked="callGroup_Checked">
....
As you can see in this code i have a listview, and items in template are radiobuttons that belongs to a groupname.
If I add a new item to the collection with the property Selected set to True it appears checked and the rest of buttons remain checked.
I solve it by getting the checkedbutton first and set it to false manually but this is not the way it's supposed to be done.
code behind:
`....
lstInCallList.ItemsSource = ContactCallList
AddHandler ContactCallList.CollectionChanged, AddressOf collectionInCall_change
.....
Public Sub collectionInCall_change(sender As Object, e As NotifyCollectionChangedEventArgs)
'Whenever collection change we must test if there is no selection and autoselect first.
If e.Action = NotifyCollectionChangedAction.Add Then
'The solution is this, but this shouldn't be necessary
'Dim seleccionado As RadioButton = getCheckedRB(lstInCallList)
'If seleccionado IsNot Nothing Then
' seleccionado.IsChecked = False
'End If
DirectCast(e.NewItems(0), PhoneCall).Selected = True
.....
End sub
`
<RadioButton IsChecked="{Binding customer.isMaleFemale}">Male</RadioButton>
<RadioButton IsChecked="{Binding customer.isMaleFemale,Converter= {StaticResource GenderConvertor}}">Female</RadioButton>
Below is the code for IValueConverter
public class GenderConvertor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return !(bool)value;
}
}
this worked for me. Even value got binded on both view and viewmodel according to the radio button click. True--> Male and False-->Female

UWP Listview CheckBox when SelectionMode.Multiple - What does it bind too?

When I set my ListView to SelectionMode.Multiple the default checkbox appears as it should - what value does this bind too?
I'm wanting this box to be either checked or unchecked depending on a value I'm setting. I cant find any references to what this default checkbox responds too.
Thanks
Well, I've tried to keep things simpler by not using a style. The below code will provide you with exactly what we have discussed in the comments but I wasn't sure if you want the selectedItems or not so in-case you do, please do let me know in the comments and I'll modify the code but from what I could understand from the comments I've drafted up a quick demo and uploaded to Github here:
The code:
I've used DataBinding to handle the Favorite instead of the itemSelected.
My xaml:
<Page.Resources>
<DataTemplate x:Name="MyItemsTemplate" x:DataType="local:ItemsClass">
<RelativePanel Background="Gray" Padding="10">
<CheckBox Name="isFavorite" RelativePanel.AlignVerticalCenterWithPanel="True" IsChecked="{x:Bind IsFavorite,Mode=TwoWay}"/>
<TextBlock x:Name="ItemNameText" Text="{x:Bind ItemName}" RelativePanel.RightOf="isFavorite"/>
<TextBlock x:Name="ItemDescText" Text="{x:Bind ItemDescription}" RelativePanel.RightOf="isFavorite" RelativePanel.Below="ItemNameText" TextWrapping="WrapWholeWords" MaxLines="2"/>
</RelativePanel>
</DataTemplate>
</Page.Resources>
<Grid Background="Black" Padding="5">
<ListView Header="Your Favorites:" ItemTemplate="{StaticResource MyItemsTemplate}" ItemsSource="{x:Bind ItemsCollection,Mode=OneWay}" ItemClick="ItemSelected" IsItemClickEnabled="True" SelectionMode="None">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="5,5"/>
<Setter Property="Padding" Value="0"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
My Code Behind:
Since i've used x:bind and it's a relatively small demo, I haven't followed the MVVM technique to keep things simple.
private ObservableCollection<ItemsClass> itemsCollection;
internal ObservableCollection<ItemsClass> ItemsCollection
{
get { return itemsCollection; }
set { itemsCollection = value; RaisePropertyChanged(nameof(ItemsCollection)); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private void ItemSelected(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is ItemsClass selectedItem)
selectedItem.IsFavorite = !selectedItem.IsFavorite;
}
The code-behind implements INotifyPropertyChanged.
My Item Backing Class:
I took up a small quick sample class to demonstrate the behavior:
internal class ItemsClass : INotifyPropertyChanged
{
public string ItemName { get; set; }
public string ItemDescription { get; set; }
private bool isFavorite;
public bool IsFavorite
{
get { return isFavorite; }
set { isFavorite = value; RaisePropertyChanged(nameof(IsFavorite)); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
I have used Visual Studio 2017 for the demo and the code that has been shared is leveraging c# 6.0 . In-case of any errors at compile time feel free to share them in the comments section.

UWP Invoke method from DataTemplate Object

Self-taught programmer, would love any constructive criticism regarding my code.
I have a ListView that will have ListViewItems that I want to customize.
The ListViewItem I have made has two TextBlocks and a ToggleSwitch. When the ToggleSwitch is switched On/Off I want it to call a method from an instantiate object, or call a method from the same form, but somehow retrieve the object that initially loaded into the DataTemplate.
Here is the XAML so far:
<ListView x:Name="listViewAddedVideoFolders" Grid.Row="1" DoubleTapped="listViewAddedVideoFolders_DoubleTapped" SelectionChanged="listViewAddedVideoFolders_SelectionChanged" HorizontalContentAlignment="Stretch">
<ListView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Center" Text="{Binding Directory}"/>
<Grid HorizontalAlignment="Right">
<StackPanel>
<TextBlock Text="Find Videos: "></TextBlock>
<ToggleSwitch Toggled="listViewVideoFolder_toggled" />
</StackPanel>
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
Right now it is calling listViewVideoFolder_toggled
Before I was trying to use Toggled="{Binding StartCrawling()}"
Here is the AddVideoFolderModel object that I am binding the listviewitems to
namespace Movie_Management_Windows_10.Models
{
public class AddVideoFolderModel
{
public static ObservableCollection<AddVideoFolderModel> MyVideoFolderModels = new ObservableCollection<AddVideoFolderModel>();
public int VideosFound { get; set; }
public string Directory { get; set; }
public string DirectoryName { get; set; }
private bool isCrawling = false;
public bool HasBeenCrawled = false;
private void startCrawling()
{
AppShell.Current.NotifyUser("Crawling began", AppShell.NotifyType.StatusMessage);
}
//public override string ToString()
//{
// return Directory + " (" + VideosFound.ToString() + ")";
//}
}
}
What must I implement to accomplish this?
At first, you can add property to your model and bind to IsOn property in ToggleSwitch using TwoWay mode binding. Is this case, your model must implement INotifyPropertyChanged
private bool _isNeedCrawle;
public bool IsNeedCrawle
{
get
{
return _isNeedCrawle;
}
set
{
if (_isNeedCrawle != value)
{
_isNeedCrawle = value;
if (_isNeedCrawle)
{
startCrawling();
}
NotifyPropretyChanged("IsNeedCrawle");
}
}
}
At second, you can use XAML Behavior SDK. In this case, you must Add reference to library (look how to do it), and change method modifier from private to public
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
<ToggleSwitch>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Toggled">
<core:CallMethodAction MethodName="StartCrawling" TargetObject="{Binding }"/>
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</ToggleSwitch>

Categories