in my WPF application I have several forms with a multitude of input fields for the user. As it turns out not every user needs all fields depending on its companie's process therefore I have the new requirement to allow the user to hide fields depending on its own needs.
I was planning to use a Behavior for this which could be attached to basically every WPF-Control. The behavior would add a ContextMenu to each of the controls allowing to show/hide all of the available Fields. My current test project which seems to work nice has four DependencyProperties to make everything work:
string VisibilityGroupName:
This acts somehow as an Id for each field but is not unique in order to group multiple fields togther (eG a Label for a field caption to its corresponding TextBox). This string is currently also used as the name that the user sees in the ContextMenu.
Dictionary VisibilityDictionary:
This dictionary keeps track of all the visibility states of the fields. In my application I'm going to serialize this to XML in order to make the users decisions persistant.
bool AllowCustomVisibility:
This is just a flag in order to switch the whole funtionality off.
bool NotificationDummy:
This is where it gets interesting. Currently I abuse this property in conjunction with an ValueChanged-event to notify all the Controls that a state was changed so they can check if they are affected. While this works as expected I know that this is just a bad workaround because I don't know how the notification would be done correctly.
Does anybody have an idea how it is done correctly? I've marked the corresponding places in the code with TODOs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace CustomizableUserControlVisibility
{
public class WPFCustomVisibilityBehavior : Behavior<DependencyObject>
{
#region Fields
private Control _control = null;
private ContextMenu _contextMenu;
private bool _contextMenuIsBuilt = false;
#endregion
#region Properties
public bool NotificationDummy
{
get { return (bool)GetValue(NotificationDummyProperty); }
set { SetValue(NotificationDummyProperty, value); }
}
public static readonly DependencyProperty NotificationDummyProperty = DependencyProperty.Register("NotificationDummy", typeof(bool), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(false));
public bool AllowCustomVisibility
{
get { return (bool)GetValue(AllowCustomVisibilityProperty); }
set { SetValue(AllowCustomVisibilityProperty, value); }
}
public static readonly DependencyProperty AllowCustomVisibilityProperty = DependencyProperty.Register("AllowCustomVisibility", typeof(bool), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(false));
public string VisibilityGroupName
{
get { return (string)GetValue(VisibilityGroupNameProperty); }
set { SetValue(VisibilityGroupNameProperty, value); }
}
public static readonly DependencyProperty VisibilityGroupNameProperty = DependencyProperty.Register("VisibilityGroupName", typeof(string), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(string.Empty));
public Dictionary<string, bool> VisibilityDictionary
{
get { return (Dictionary<string, bool>)GetValue(VisibilityDictionaryProperty); }
set { SetValue(VisibilityDictionaryProperty, value); }
}
public static readonly DependencyProperty VisibilityDictionaryProperty = DependencyProperty.Register("VisibilityDictionary", typeof(Dictionary<string, bool>), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(new Dictionary<string, bool>()));
#endregion
#region Constructor
public WPFCustomVisibilityBehavior()
{
// TODO: There should be a better way to notify other controls about state changes than this...
var temp = DependencyPropertyDescriptor.FromProperty(WPFCustomVisibilityBehavior.NotificationDummyProperty, typeof(WPFCustomVisibilityBehavior));
if (temp != null)
{
temp.AddValueChanged(this, OnNotificationDummyChanged);
}
}
#endregion
#region Overrrides
protected override void OnAttached()
{
base.OnAttached();
if (this.AllowCustomVisibility == false)
{
return;
}
this._control = this.AssociatedObject as Control;
if (!string.IsNullOrEmpty(this.VisibilityGroupName) && this._control != null)
{
if (this.VisibilityDictionary.ContainsKey(this.VisibilityGroupName))
{
if (this.VisibilityDictionary[this.VisibilityGroupName])
{
this._control.Visibility = Visibility.Visible;
}
else
{
this._control.Visibility = Visibility.Collapsed;
}
}
else
{
this.VisibilityDictionary.Add(this.VisibilityGroupName, this._control.Visibility == Visibility.Visible ? true : false);
}
// Add a ContextMenu to the Control, but only if it does not already have one (TextBox brings its default ContextMenu for copy, cut and paste)
if (this._control.ContextMenu == null && !(this._control is TextBox))
{
this._contextMenu = new ContextMenu();
ContextMenuService.SetContextMenu(this._control, this._contextMenu);
this._control.ContextMenuOpening += (sender, e) => { ContextMenuOpening(e); };
}
}
}
#endregion
#region Event handling
private void ContextMenuOpening(ContextMenuEventArgs e)
{
if (this._contextMenuIsBuilt == false)
{
this._contextMenu.Items.Clear();
Dictionary<string, MenuItem> menuItems = new Dictionary<string, MenuItem>();
foreach (string k in this.VisibilityDictionary.Keys)
{
MenuItem menuItem = new MenuItem() { Header = k, Name = k, IsCheckable = true, StaysOpenOnClick = true };
menuItem.Click += MenuItem_Click;
menuItems.Add(k, menuItem);
}
var keyList = menuItems.Keys.ToList();
keyList.Sort();
foreach (string key in keyList)
{
this._contextMenu.Items.Add(menuItems[key]);
}
this._contextMenuIsBuilt = true;
}
foreach (MenuItem mi in this._contextMenu.Items)
{
mi.IsChecked = this.VisibilityDictionary[mi.Name];
}
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null && this.VisibilityDictionary != null && this.VisibilityDictionary.ContainsKey(mi.Name))
{
this.VisibilityDictionary[mi.Name] = mi.IsChecked;
// TODO: There should be a better way to notify other controls about state changes than this...
this.NotificationDummy = !NotificationDummy;
}
}
private void OnNotificationDummyChanged(object sender, EventArgs args)
{
// TODO: There should be a better way to notify other controls about state changes than this...
if (this._control != null && this.VisibilityDictionary != null && !string.IsNullOrEmpty(this.VisibilityGroupName))
{
if (this.VisibilityDictionary.ContainsKey(this.VisibilityGroupName))
{
if (this.VisibilityDictionary[this.VisibilityGroupName])
{
this._control.Visibility = Visibility.Visible;
}
else
{
this._control.Visibility = Visibility.Collapsed;
}
}
}
}
#endregion
}
}
Due to the lack of any other ideas I decided to use a static Event which seems to solve my problem quite well and this approach at leasts saves me the NotificationDummy-DependencyProperty which I had to use in the first place.
If anybody is interested - here is my final solution:
namespace CustomizableUserControlVisibility
{
public delegate void VisibilityChangedEventHandler(object visibilityDictionary);
public class WPFCustomVisibilityBehavior : Behavior<DependencyObject>
{
#region Fields
public static event VisibilityChangedEventHandler OnVisibilityChanged;
private Control _control = null;
private ContextMenu _contextMenu;
private bool _contextMenuIsBuilt = false;
#endregion
#region Properties
public bool AllowCustomVisibility
{
get { return (bool)GetValue(AllowCustomVisibilityProperty); }
set { SetValue(AllowCustomVisibilityProperty, value); }
}
public static readonly DependencyProperty AllowCustomVisibilityProperty = DependencyProperty.Register("AllowCustomVisibility", typeof(bool), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(false));
public string VisibilityGroupName
{
get { return (string)GetValue(VisibilityGroupNameProperty); }
set { SetValue(VisibilityGroupNameProperty, value); }
}
public static readonly DependencyProperty VisibilityGroupNameProperty = DependencyProperty.Register("VisibilityGroupName", typeof(string), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(string.Empty));
public Dictionary<string, bool> VisibilityDictionary
{
get { return (Dictionary<string, bool>)GetValue(VisibilityDictionaryProperty); }
set { SetValue(VisibilityDictionaryProperty, value); }
}
public static readonly DependencyProperty VisibilityDictionaryProperty = DependencyProperty.Register("VisibilityDictionary", typeof(Dictionary<string, bool>), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(new Dictionary<string, bool>()));
#endregion
#region Constructor
public WPFCustomVisibilityBehavior()
{
OnVisibilityChanged += VisibilityChanged;
}
#endregion
#region Overrrides
protected override void OnAttached()
{
base.OnAttached();
if (this.AllowCustomVisibility == false)
{
return;
}
this._control = this.AssociatedObject as Control;
if (!string.IsNullOrEmpty(this.VisibilityGroupName) && this._control != null)
{
if (this.VisibilityDictionary.ContainsKey(this.VisibilityGroupName))
{
if (this.VisibilityDictionary[this.VisibilityGroupName])
{
this._control.Visibility = Visibility.Visible;
}
else
{
this._control.Visibility = Visibility.Collapsed;
}
}
else
{
this.VisibilityDictionary.Add(this.VisibilityGroupName, this._control.Visibility == Visibility.Visible ? true : false);
}
}
// Add a ContextMenu to the Control, but only if it does not already have one (TextBox brings its default ContextMenu for copy, cut and paste)
if (this._control != null && this._control.ContextMenu == null && !(this._control is TextBox))
{
this._contextMenu = new ContextMenu();
ContextMenuService.SetContextMenu(this._control, this._contextMenu);
this._control.ContextMenuOpening += (sender, e) => { ContextMenuOpening(e); };
}
}
#endregion
#region Event handling
private void ContextMenuOpening(ContextMenuEventArgs e)
{
if (this._contextMenuIsBuilt == false)
{
// Clear Items just to be sure there is nothing in it...
this._contextMenu.Items.Clear();
// Create default items first
MenuItem showAll = new MenuItem() { Header = "Show all optional fields", IsCheckable = false, FontWeight = FontWeights.Bold };
showAll.Click += MenuItem_ShowAll_Click;
MenuItem hideAll = new MenuItem() { Header = "Hide all optional fields", IsCheckable = false, FontWeight = FontWeights.Bold };
hideAll.Click += MenuItem_HideAll_Click;
// Create field items and sort them by name
Dictionary<string, MenuItem> menuItems = new Dictionary<string, MenuItem>();
foreach (string k in this.VisibilityDictionary.Keys)
{
MenuItem menuItem = new MenuItem() { Header = k, Name = k, IsCheckable = true, StaysOpenOnClick = true };
menuItem.Click += MenuItem_Click;
menuItems.Add(k, menuItem);
}
var keyList = menuItems.Keys.ToList();
keyList.Sort();
// Now add default items followed by field items
this._contextMenu.Items.Add(showAll);
this._contextMenu.Items.Add(hideAll);
this._contextMenu.Items.Add(new Separator());
foreach (string key in keyList)
{
this._contextMenu.Items.Add(menuItems[key]);
}
this._contextMenuIsBuilt = true;
}
foreach (Object o in this._contextMenu.Items)
{
MenuItem mi = o as MenuItem;
if (mi != null && mi.FontWeight != FontWeights.Bold)
{
mi.IsChecked = this.VisibilityDictionary[mi.Name];
}
}
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null && this.VisibilityDictionary != null && this.VisibilityDictionary.ContainsKey(mi.Name))
{
this.VisibilityDictionary[mi.Name] = mi.IsChecked;
OnVisibilityChanged(this.VisibilityDictionary);
}
}
private void MenuItem_HideAll_Click(object sender, RoutedEventArgs e)
{
List<string> keys = this.VisibilityDictionary.Keys.ToList<string>();
foreach (string key in keys)
{
this.VisibilityDictionary[key] = false;
}
OnVisibilityChanged(this.VisibilityDictionary);
}
private void MenuItem_ShowAll_Click(object sender, RoutedEventArgs e)
{
List<string> keys = this.VisibilityDictionary.Keys.ToList<string>();
foreach (string key in keys)
{
this.VisibilityDictionary[key] = true;
}
OnVisibilityChanged(this.VisibilityDictionary);
}
private void VisibilityChanged(object visibilityDictionary)
{
if (this._control != null && this.VisibilityDictionary != null && this.VisibilityDictionary == visibilityDictionary && !string.IsNullOrEmpty(this.VisibilityGroupName))
{
if (this.VisibilityDictionary.ContainsKey(this.VisibilityGroupName))
{
if (this.VisibilityDictionary[this.VisibilityGroupName])
{
this._control.Visibility = Visibility.Visible;
}
else
{
this._control.Visibility = Visibility.Collapsed;
}
}
}
}
#endregion
}
}
Related
I have a custom combobox in my WPF application and the combo box is not updating when my item source is updated after initial launch. The itemsSource is a custom observableDictionary. I've implemented INotifyPropertyChanged on my properties with my observableDictionary but it won't update. I've searched through every WPF propertychanged event not working on stack overflow and i'm looking for some assistance.
Custom Combobox Code:
<controls:MultiSelectComboBox Width="100" Height="30" Padding="0 10 0 0" Grid.Row="0" Grid.Column="1"
ItemsSource="{Binding Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True,
Path=DataContext.OptionTeamMembersDictionary,
BindsDirectlyToSource=True,
RelativeSource={RelativeSource AncestorType={x:Type UserControl},Mode=FindAncestor}}"
SelectedItems="{Binding SelectedOptionTeamMembersDictionary, Mode=TwoWay}"
x:Name="TeamMemberDisplay"
ToolTip="{Binding Path=Text, RelativeSource={RelativeSource Self}}"/>
Properties and private variables:
private ObservableDictionary<string, object> _optionTeamMembersDictionary;
private ObservableDictionary<string, object> _selectedOptionTeamMembersDictionary;
public ObservableDictionary<string, object> OptionTeamMembersDictionary
{
get
{
return _optionTeamMembersDictionary;
}
set
{
_optionTeamMembersDictionary = value;
OnPropertyChanged("OptionTeamMembersDictionary");
}
}
public ObservableDictionary<string, object> SelectedOptionTeamMembersDictionary
{
get
{
return _selectedOptionTeamMembersDictionary;
}
set
{
_selectedOptionTeamMembersDictionary = value;
OnPropertyChanged("SelectedOptionTeamMembersDictionary");
}
}
I use a button to trigger a database pull which leads each row into an object and then populates the optionTeamMemberDictionary when it returns more then one row of data.
All of the above works when the data is loaded in my constructor but when its loaded after launch my comboboxes do not show the new data in the collection.
EDIT:
Code for the Custom ComboBox being used:
https://www.codeproject.com/Articles/563862/Multi-Select-ComboBox-in-WPF is the URL this came from. Some edits were made to implement an obserableDirctionary instead of normal dictionary
public partial class MultiSelectComboBox : UserControl
{
private ObservableCollection<Node> _nodeList;
public MultiSelectComboBox()
{
InitializeComponent();
_nodeList = new ObservableCollection<Node>();
}
#region Dependency Properties
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(ObservableDictionary<string, object>), typeof(MultiSelectComboBox), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(MultiSelectComboBox.OnItemsSourceChanged)));
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(ObservableDictionary<string, object>), typeof(MultiSelectComboBox), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(MultiSelectComboBox.OnSelectedItemsChanged)));
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MultiSelectComboBox), new UIPropertyMetadata(string.Empty));
public static readonly DependencyProperty DefaultTextProperty =
DependencyProperty.Register("DefaultText", typeof(string), typeof(MultiSelectComboBox), new UIPropertyMetadata(string.Empty));
public ObservableDictionary<string, object> ItemsSource
{
get { return (ObservableDictionary<string, object>)GetValue(ItemsSourceProperty); }
set
{
SetValue(ItemsSourceProperty, value);
}
}
public ObservableDictionary<string, object> SelectedItems
{
get { return (ObservableDictionary<string, object>)GetValue(SelectedItemsProperty); }
set
{
SetValue(SelectedItemsProperty, value);
}
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public string DefaultText
{
get { return (string)GetValue(DefaultTextProperty); }
set { SetValue(DefaultTextProperty, value); }
}
#endregion
#region Events
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MultiSelectComboBox control = (MultiSelectComboBox)d;
control.DisplayInControl();
}
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MultiSelectComboBox control = (MultiSelectComboBox)d;
control.SelectNodes();
control.SetText();
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
CheckBox clickedBox = (CheckBox)sender;
if (clickedBox.Content == "All")
{
if (clickedBox.IsChecked.Value)
{
foreach (Node node in _nodeList)
{
node.IsSelected = true;
}
}
else
{
foreach (Node node in _nodeList)
{
node.IsSelected = false;
}
}
}
else
{
int _selectedCount = 0;
foreach (Node s in _nodeList)
{
if (s.IsSelected && s.Title != "All")
_selectedCount++;
}
if (_selectedCount == _nodeList.Count - 1)
_nodeList.FirstOrDefault(i => i.Title == "All").IsSelected = true;
else
_nodeList.FirstOrDefault(i => i.Title == "All").IsSelected = false;
}
SetSelectedItems();
SetText();
}
#endregion
#region Methods
private void SelectNodes()
{
foreach (KeyValuePair<string, object> keyValue in SelectedItems)
{
Node node = _nodeList.FirstOrDefault(i => i.Title == keyValue.Key);
if (node != null)
node.IsSelected = true;
}
}
private void SetSelectedItems()
{
if (SelectedItems == null)
SelectedItems = new ObservableDictionary<string, object>();
SelectedItems.Clear();
foreach (Node node in _nodeList)
{
if (node.IsSelected && node.Title != "All")
{
if (this.ItemsSource.Count > 0)
SelectedItems.Add(node.Title, this.ItemsSource[node.Title]);
}
}
}
private void DisplayInControl()
{
_nodeList.Clear();
if (this.ItemsSource.Count > 0)
_nodeList.Add(new Node("All"));
foreach (KeyValuePair<string, object> keyValue in this.ItemsSource)
{
Node node = new Node(keyValue.Key);
_nodeList.Add(node);
}
MultiSelectCombo.ItemsSource = _nodeList;
}
private void SetText()
{
if (this.SelectedItems != null)
{
StringBuilder displayText = new StringBuilder();
foreach (Node s in _nodeList)
{
if (s.IsSelected == true && s.Title == "All")
{
displayText = new StringBuilder();
displayText.Append("All");
break;
}
else if (s.IsSelected == true && s.Title != "All")
{
displayText.Append(s.Title);
displayText.Append(',');
}
}
this.Text = displayText.ToString().TrimEnd(new char[] { ',' });
}
// set DefaultText if nothing else selected
if (string.IsNullOrEmpty(this.Text))
{
this.Text = this.DefaultText;
}
}
#endregion
}
public class Node : INotifyPropertyChanged
{
private string _title;
private bool _isSelected;
#region ctor
public Node(string title)
{
Title = title;
}
#endregion
#region Properties
public string Title
{
get
{
return _title;
}
set
{
_title = value;
NotifyPropertyChanged("Title");
}
}
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
After much troubleshooting i was able to figure out an answer for this. onItemsSourceChanged was not firing after the control was instantiated so it never got updates made after initial launch of the application. I edited the OnItemsSourceChanged Function on the MultiFunctionComboBox to the below so it would fire on a changed event and it is working as expected now.
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MultiSelectComboBox control = (MultiSelectComboBox)d;
var action = new NotifyCollectionChangedEventHandler(
(o, args) =>
{
MultiSelectComboBox c = (MultiSelectComboBox)d;
c?.DisplayInControl();
});
if (e.OldValue != null)
{
var sourceCollection = (ObservableDictionary<string, object>)e.OldValue;
sourceCollection.CollectionChanged -= action;
}
if (e.NewValue != null)
{
var sourceCollection = (ObservableDictionary<string, object>)e.NewValue;
sourceCollection.CollectionChanged += action;
}
control.DisplayInControl();
}
I am new at Xamarin forms and renderers, Can you please help me out in this,
Thank you in advance.
I have created a checkbox but I want to change its background color like in picture.
Now you can see in image default checkbox is there I want to change its background and style how can I do that?
<local:Checkbox x:Name="chkBrand" AutomationId="AutoIdCheckBox"
Content="{Binding LblBrand}"
Checked="{Binding Chk,Mode=TwoWay}">
</local:Checkbox>
Checkbox.cs
public class Checkbox : Xamarin.Forms.View
{
public static readonly BindableProperty CheckedProperty =
BindableProperty.Create("Checked",
typeof(bool),
typeof(Checkbox),
false, BindingMode.TwoWay, propertyChanged: OnCheckedPropertyChanged);
public static readonly BindableProperty ContentProperty =
BindableProperty.Create("Content",
typeof(string),
typeof(Checkbox),
"Content", BindingMode.OneWay);
public static readonly BindableProperty FontSizeProperty =
BindableProperty.Create("FontSize",
typeof(double),
typeof(Checkbox),
default(double), BindingMode.OneWay);
public bool Checked
{
get
{
return (bool)GetValue(CheckedProperty);
}
set
{
if (this.Checked != value)
{
SetValue(CheckedProperty, value);
if (CheckedChanged != null)
CheckedChanged.Invoke(this, new CheckedChangedEventArgs(value));
}
}
}
public string Content
{
get
{
return (string)GetValue(ContentProperty);
}
set
{
SetValue(ContentProperty, value);
}
}
public double FontSize
{
get
{
return (double)GetValue(FontSizeProperty);
}
set
{
SetValue(FontSizeProperty, value);
}
}
public event EventHandler<CheckedChangedEventArgs> CheckedChanged;
private static void OnCheckedPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var checkBox = (Checkbox)bindable;
checkBox.Checked = newvalue != null ? (bool)newvalue : false;
}
}
and platform wise renderer are used. Adding UWP renderer for reference
public class CheckboxRenderer : ViewRenderer<Checkbox, CheckBox>
{
public new static void Init()
{
var temp = DateTime.Now;
}
protected override void OnElementChanged(ElementChangedEventArgs<Checkbox> e)
{
base.OnElementChanged(e);
if (Element == null)
return;
if (Control == null)
{
var checkBox = new CheckBox();
checkBox.Checked += CheckBox_Checked;
checkBox.Unchecked += CheckBox_Unchecked;
SetNativeControl(checkBox);
}
if (e.NewElement != null)
{
Control.Content = e.NewElement.Content;
Control.IsChecked = e.NewElement.Checked;
Control.IsEnabled = e.NewElement.IsEnabled;
if (default(double) != e.NewElement.FontSize)
Control.FontSize = e.NewElement.FontSize;
}
}
private void CheckBox_Unchecked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
Element.Checked = Control.IsChecked.GetValueOrDefault();
}
private void CheckBox_Checked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
Element.Checked = Control.IsChecked.GetValueOrDefault();
}
private void CheckBox_Content(object sender)
{
Element.Checked = Control.IsChecked.GetValueOrDefault();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
switch (e.PropertyName)
{
//case "IsVisible":
// Control.Hidden = Element.IsVisible;
// break;
case "IsEnabled":
Control.IsEnabled = Element.IsEnabled;
break;
case "Checked":
Control.IsChecked = Element.Checked;
break;
case "Content":
Control.Content = Element.Content;
break;
}
}
There are two ways to do this,
Use nuget package to show check box or
A simple way to change the style is to use the image control for checkbox.
Take two images as per your colour, one checked and other one is unchecked and do hide & show on click.
I use TestStack White framework to automate testing of a WPF Application
It needs to open modal window and access TextBox in it. Everything works well, but White can't find Textbox, although it finds other elements of window
I tried the following lines of code:
TestStack.White.UIItems.TextBox TextBox = CreateBranch.Get<TestStack.White.UIItems.TextBox>(SearchCriteria.byAutomationId("Title"));
where CreateBranch is modal window
I also tried (SearchCriteria.All), (SearchCriteria.ByControlType) and nothing works
Coded UI tool finds this element well by AutomationID, but I need to do it in White
UISpy and other similar tools recognize this control and see its AutomationID
This textbox is custom control, here's code for it, I changed namespace name for privacy:
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace Test.Wpf.Controls.XTextBox
{
[TemplatePart(Name = "PART_Watermark", Type = typeof(TextBlock))]
[TemplatePart(Name = "PART_Pasword", Type = typeof(TextBlock))]
public class XTextBox : TextBox
{
#region Static
static XTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(XTextBox), new FrameworkPropertyMetadata(typeof(XTextBox)));
}
#endregion //Static
#region Fields
private TextBlock PART_Watermark;
private TextBlock PART_Pasword;
#endregion //Fields
#region DependencyProperties
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register(
"Watermark",
typeof(String),
typeof(XTextBox),
new PropertyMetadata(String.Empty));
public static readonly DependencyProperty WatermarkVerticalAlignmentProperty = DependencyProperty.Register(
"WatermarkVerticalAlignment",
typeof(VerticalAlignment),
typeof(XTextBox),
new PropertyMetadata(VerticalAlignment.Stretch));
public static readonly DependencyProperty WatermarkForegroundProperty = DependencyProperty.Register(
"WatermarkForeground",
typeof(Brush),
typeof(XTextBox),
new PropertyMetadata(new SolidColorBrush(Colors.Black)));
public static readonly DependencyProperty WatermarkFontSizeProperty = DependencyProperty.Register(
"WatermarkFontSize",
typeof(Double),
typeof(XTextBox),
new PropertyMetadata(12.0));
public static readonly DependencyProperty IsFloatingProperty = DependencyProperty.Register(
"IsFloating",
typeof(Boolean),
typeof(XTextBox),
new PropertyMetadata(false));
public static readonly DependencyProperty IsAccessNegativeProperty = DependencyProperty.Register(
"IsAccessNegative",
typeof(Boolean),
typeof(XTextBox),
new PropertyMetadata(true));
public static readonly DependencyProperty IsDigitOnlyProperty = DependencyProperty.Register(
"IsDigitOnly",
typeof(Boolean),
typeof(XTextBox),
new PropertyMetadata(false));
public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register(
"MaxValue",
typeof(Single),
typeof(XTextBox),
new PropertyMetadata(Single.NaN));
public static readonly DependencyProperty IsPasswordProperty = DependencyProperty.Register(
"IsPassword",
typeof(Boolean),
typeof(XTextBox),
new PropertyMetadata(false));
public static readonly DependencyProperty VisibilityMainTextProperty = DependencyProperty.Register(
"VisibilityMainText",
typeof(Visibility),
typeof(XTextBox),
new PropertyMetadata(Visibility.Visible));
#endregion //DependencyProperties
#region Properties
[Description("Gets or sets the watermark title")]
public String Watermark
{
get { return (String)GetValue(WatermarkProperty); }
set { SetValue(WatermarkProperty, value); }
}
[Description("Gets or sets the watermark vertical alignment")]
public VerticalAlignment WatermarkVerticalAlignment
{
get { return (VerticalAlignment)GetValue(WatermarkVerticalAlignmentProperty); }
set { SetValue(WatermarkVerticalAlignmentProperty, value); }
}
[Description("Gets or sets the watermark title color")]
public Brush WatermarkForeground
{
get { return (Brush)GetValue(WatermarkVerticalAlignmentProperty); }
set { SetValue(WatermarkVerticalAlignmentProperty, value); }
}
[Description("Gets or sets the watermark title font size")]
public Double WatermarkFontSize
{
get { return (Double)GetValue(WatermarkVerticalAlignmentProperty); }
set { SetValue(WatermarkVerticalAlignmentProperty, value); }
}
[Description("Gets or sets the textbox floating mode")]
public Boolean IsFloating
{
get { return (Boolean)GetValue(IsFloatingProperty); }
set { SetValue(IsFloatingProperty, value); }
}
[Description("Gets or sets the textbox access of negative values")]
public Boolean IsAccessNegative
{
get { return (Boolean)GetValue(IsAccessNegativeProperty); }
set { SetValue(IsAccessNegativeProperty, value); }
}
[Description("Gets or sets the textbox chars type")]
public Boolean IsDigitOnly
{
get { return (Boolean)GetValue(IsDigitOnlyProperty); }
set { SetValue(IsDigitOnlyProperty, value); }
}
[Description("Gets or sets the max input value (enable in digit mode only)")]
public Single MaxValue
{
get { return (Single)GetValue(MaxValueProperty); }
set { SetValue(MaxValueProperty, value); }
}
[Description("Gets or sets the textbox is passwordbox")]
public Boolean IsPassword
{
get { return (Boolean)GetValue(IsPasswordProperty); }
set { SetValue(IsPasswordProperty, value); }
}
public Visibility VisibilityMainText
{
get { return (Visibility)GetValue(VisibilityMainTextProperty); }
set { SetValue(VisibilityMainTextProperty, value); }
}
#endregion //Properties
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
PART_Watermark = GetTemplateChild("PART_Watermark") as TextBlock;
PART_Pasword = GetTemplateChild("PART_Pasword") as TextBlock;
SetWatermarkVisibility();
if (IsPassword)
{
VisibilityMainText = Visibility.Collapsed;
if (PART_Pasword != null)
{
PART_Pasword.Visibility = Visibility.Visible;
PART_Pasword.FontSize = 20;
}
}
else
{
VisibilityMainText = Visibility.Visible;
}
DataObject.AddPastingHandler(this, OnPaste);
}
protected void OnPaste(Object sender, DataObjectPastingEventArgs e)
{
try
{
var isText = e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true);
if (!isText) return;
var text = e.SourceDataObject.GetData(DataFormats.UnicodeText) as String;
if (!String.IsNullOrEmpty(text))
{
if (IsDigitOnly)
{
if (!IsAccessNegative)
{
var ch = text[0];
if (ch == 45)
{
e.CancelCommand();
}
}
for (int i = 0; i < text.Length; i++)
{
if (i == 0)
{
if (IsAccessNegative && text[i] == 45)
{
continue;
}
}
if (!Char.IsDigit(text[0]))
e.CancelCommand();
}
}
}
}
catch (Exception)
{
// ignored
e.Handled = true;
}
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
SetWatermarkVisibility();
if (IsPassword)
{
PART_Pasword.Text = new String('•', Text.Length);
}
}
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnLostKeyboardFocus(e);
SetWatermarkVisibility();
}
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnGotKeyboardFocus(e);
if (PART_Watermark != null)
{
PART_Watermark.Visibility = Visibility.Hidden;
}
}
protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
base.OnPreviewTextInput(e);
if (e.Text.Length == 0)
{
e.Handled = true;
return;
}
if (IsDigitOnly)
{
var ch = e.Text[0];
if (!Char.IsDigit(ch) && ch != 8 && ch != 46)
{
if (!(IsAccessNegative && ch == 45))
e.Handled = true;
}
if (IsFloating)
{
if (ch == 46 && Text.IndexOf('.') != -1)
{
e.Handled = true;
return;
}
}
if (!IsAccessNegative)
{
if (ch == 45)
{
e.Handled = true;
}
}
}
}
#region Private
private void SetWatermarkVisibility()
{
if (PART_Watermark != null)
{
PART_Watermark.Visibility = (Text != String.Empty || IsKeyboardFocused)? Visibility.Hidden : Visibility.Visible;
}
}
#endregion
}
}
Screenshot from UISpy
Let me know if that works
TextBox = (TextBox)CreateBranch
.Get(SearchCriteria.ByAutomationId("Title").AndOfFramework(WindowsFramework.Wpf));
Edited after new source added
You have to create a specific AutomationPeer for your custom control and return it via the override of the method OnCreateAutomationPeer().
Your control is a subclass of the TextBox control so you can just return a new TextBoxAutomationPeer instance or create your custom AutomationPeer from it.
public class XTextBox : TextBox
{
...
protected override AutomationPeer OnCreateAutomationPeer()
{
return new XTextBoxAutomationPeer(this);
// or just use the TextBoxAutomationPeer
// return new TextBoxAutomationPeer(this);
}
...
}
The custom automation peer
public class XTextBoxAutomationPeer : TextBoxAutomationPeer
{
public XTextBoxAutomationPeer(XTextBox owner)
: base(owner)
{
}
protected override string GetClassNameCore()
{
return "XTextBox";
}
}
[SetUpFixture]
public class SETUP_THAT_WILL_GET_CALL_LATER
{
[OneTimeSetUp]
public void OneTimeSetUp()
{
var applicationDirectory = TestContext.CurrentContext.TestDirectory;
var applicationPath = Path.Combine(applicationDirectory, #"..\..\..\, "your debug folder path here", "your application.exe here");
Application = Application.Launch(applicationPath);
Thread.Sleep(2000);
Window = Application.GetWindow("Title of your application", InitializeOption.WithCache);
}
[OneTimeTearDown()]
public void OneTimeTearDown()
{
Window.Dispose();
Application.Dispose();
}
public static Application Application;
public static Window Window;
}
Then in your test
[Test]
public void yourtest()
{
var textBox = SETUP_THAT_WILL_GET_CALL_LATER.**Window.Get<TextBox>("Your textbox name here");**
}
I have a strange situation.
I working on an App for WINRT and have some problems with a command binding. This is the part of the xaml:
<control:ItemsHub ItemsSource="{Binding Categories}">
<control:ItemsHub.SectionHeaderTemplate>
<DataTemplate>
<Button Command="{Binding CategoryNavigationCommand}" Margin="5,0,0,10" Content="{Binding Header}"/>
</DataTemplate>
</control:ItemsHub.SectionHeaderTemplate>
</control:ItemsHub>
This is my ViewModel:
public CategorySectionViewModel(IRecipeService recipeService, INavigationService navigationService, RecipeTreeItemDto treeItem)
{
...
CategoryNavigationCommand = new DelegateCommand(NavigateToCategory);
...
}
private string _header;
public string Header
{
get { return _header; }
set { SetProperty(ref _header, value); }
}
public DelegateCommand CategoryNavigationCommand { get; private set; }
private void NavigateToCategory()
{
_navigationService.Navigate("CategoryHub", _recipeTreeItemDto.NodePath);
}
I don't get any binding errors in the output window and also the "Header" gets shown in the Button. But the Command won't get fired wenn i click on it! What i'm doing wrong?
Maybe it is because i created a custom HubControl. With this control i'm able to attach an ItemSource and ItemTemplate for the HubSection-Header and the HubSection-Content. Maybe because of this some bindings getting lost?
Here is my custom hub control:
public class ItemsHub : Hub
{
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
public IList ItemsSource
{
get { return (IList)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public HubSection SpotlightSection
{
get { return (HubSection)GetValue(SpotlightSectionProperty); }
set { SetValue(SpotlightSectionProperty, value); }
}
public DataTemplate SectionHeaderTemplate
{
get { return (DataTemplate)GetValue(SectionHeaderTemplateProperty); }
set { SetValue(SectionHeaderTemplateProperty, value); }
}
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(ItemsHub), new PropertyMetadata(null, ItemTemplateChanged));
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IList), typeof(ItemsHub), new PropertyMetadata(null, ItemsSourceChanged));
public static readonly DependencyProperty SpotlightSectionProperty =
DependencyProperty.Register("SpotlightSection", typeof(HubSection), typeof(ItemsHub), new PropertyMetadata(default(HubSection), SpotlightSectionChanged));
public static readonly DependencyProperty SectionHeaderTemplateProperty =
DependencyProperty.Register("SectionHeaderTemplate", typeof(DataTemplate), typeof(ItemsHub), new PropertyMetadata(default(DataTemplate), HeaderTemplateChanged));
private static void SpotlightSectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ItemsHub hub = d as ItemsHub;
if (hub != null)
{
bool hubContainsSpotLight = hub.Sections.Contains(hub.SpotlightSection);
if (hub.SpotlightSection != null && !hubContainsSpotLight)
{
hub.Sections.Add(hub.SpotlightSection);
}
if (hub.SpotlightSection == null && hubContainsSpotLight)
{
hub.Sections.Remove(hub.SpotlightSection);
}
}
}
private static void HeaderTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ItemsHub hub = d as ItemsHub;
if (hub != null)
{
DataTemplate template = e.NewValue as DataTemplate;
if (template != null)
{
// Apply template
foreach (var section in hub.Sections.Except(new List<HubSection> { hub.SpotlightSection }))
{
section.HeaderTemplate = template;
}
}
}
}
private static void ItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ItemsHub hub = d as ItemsHub;
if (hub != null)
{
DataTemplate template = e.NewValue as DataTemplate;
if (template != null)
{
// Apply template
foreach (var section in hub.Sections.Except(new List<HubSection> { hub.SpotlightSection }))
{
section.ContentTemplate = template;
}
}
}
}
private static void ItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ItemsHub hub = d as ItemsHub;
if (hub != null)
{
IList items = e.NewValue as IList;
if (items != null)
{
var spotLightSection = hub.SpotlightSection;
hub.Sections.Clear();
if (spotLightSection != null)
{
hub.Sections.Add(spotLightSection);
}
foreach (var item in items)
{
HubSection section = new HubSection();
section.DataContext = item;
section.Header = item;
DataTemplate headerTemplate = hub.SectionHeaderTemplate;
section.HeaderTemplate = headerTemplate;
DataTemplate contentTemplate = hub.ItemTemplate;
section.ContentTemplate = contentTemplate;
hub.Sections.Add(section);
}
}
}
}
}
Thanks for your help!
I found the solution! I don't know why but the section header of a hub control is not interactive by default! So i have to set the interactivity to true when i assign the itemsource in my custom ItemHub-Control.
Here is what i changed in the ItemsHub Control:
private static void ItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ItemsHub hub = d as ItemsHub;
if (hub != null)
{
IList items = e.NewValue as IList;
if (items != null)
{
var spotLightSection = hub.SpotlightSection;
hub.Sections.Clear();
if (spotLightSection != null)
{
hub.Sections.Add(spotLightSection);
}
foreach (var item in items)
{
HubSection section = new HubSection();
DataTemplate headerTemplate = hub.SectionHeaderTemplate;
section.HeaderTemplate = headerTemplate;
DataTemplate contentTemplate = hub.ItemTemplate;
section.ContentTemplate = contentTemplate;
section.DataContext = item;
section.Header = item;
//This line fixed my problem.
section.IsHeaderInteractive = true;
hub.Sections.Add(section);
}
}
}
You can use following binding expression
Command="{Binding CategoryNavigationCommand,RelativeSource={RelativeSource AncestorType=XYX}}"
and XYZ is a control type in which you have placed ItemsHub
I'm currently working on databinding some of my existing Windows Forms, and I've ran into an issue figuring out the proper way of databinding a group of radiobutton controls within a group box.
My business object has an integer property which I want to databind against 4 radiobuttons (where each of them represents the values 0 - 3).
I'm currently binding against a presenter object which works as the binder between the form and the business object, and the way I've done it now is to have 4 separate properties which each binds against each of these values (I do use INotifyPropertyChanged, but not including that here):
Private int _propValue;
Public bool PropIsValue0
{
get { return _propValue == 0; }
set
{
if (value)
_propValue = 0;
}
}
Public bool PropIsValue1 { // As above, but with value == 1 }
Public bool PropIsValue2 { // As above, but with value == 2 }
Public bool PropIsValue3 { // As above, but with value == 3 }
And I then bind each of the radiobuttons to their respective property as above.
This does not seem right to me, so any advice are highly appreciated.
Following is a generic RadioGroupBox implementation in the spirit of ArielBH's suggestion (some code borrowed from Jay Andrew Allen's RadioPanel). Just add RadioButtons to it, set their tags to different integers and bind to the 'Selected' property.
public class RadioGroupBox : GroupBox
{
public event EventHandler SelectedChanged = delegate { };
int _selected;
public int Selected
{
get
{
return _selected;
}
set
{
int val = 0;
var radioButton = this.Controls.OfType<RadioButton>()
.FirstOrDefault(radio =>
radio.Tag != null
&& int.TryParse(radio.Tag.ToString(), out val) && val == value);
if (radioButton != null)
{
radioButton.Checked = true;
_selected = val;
}
}
}
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
var radioButton = e.Control as RadioButton;
if (radioButton != null)
radioButton.CheckedChanged += radioButton_CheckedChanged;
}
void radioButton_CheckedChanged(object sender, EventArgs e)
{
var radio = (RadioButton)sender;
int val = 0;
if (radio.Checked && radio.Tag != null
&& int.TryParse(radio.Tag.ToString(), out val))
{
_selected = val;
SelectedChanged(this, new EventArgs());
}
}
}
Note that you can't bind to the 'Selected' property via the designer due to initialization order problems in InitializeComponent (the binding is performed before the radio buttons are initialized, so their tag is null in the first assignment). So just bind yourself like so:
public Form1()
{
InitializeComponent();
//Assuming selected1 and selected2 are defined as integer application settings
radioGroup1.DataBindings.Add("Selected", Properties.Settings.Default, "selected1");
radioGroup2.DataBindings.Add("Selected", Properties.Settings.Default, "selected2");
}
I know this post is old but in my search for an answer for this same problem I came across this post and it didn't solve my problem. I ended up having a lightbulb go off randomly just a minute ago and wanted to share my solution.
I have three radio buttons in a group box. I'm using a List<> of a custom class object as the data source.
Class Object:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BAL
{
class ProductItem
{
// Global Variable to store the value of which radio button should be checked
private int glbTaxStatus;
// Public variable to set initial value passed from
// database query and get value to save to database
public int TaxStatus
{
get { return glbTaxStatus; }
set { glbTaxStatus = value; }
}
// Get/Set for 1st Radio button
public bool Resale
{
// If the Global Variable = 1 return true, else return false
get
{
if (glbTaxStatus.Equals(1))
{
return true;
}
else
{
return false;
}
}
// If the value being passed in = 1 set the Global Variable = 1, else do nothing
set
{
if (value.Equals(true))
{
glbTaxStatus = 1;
}
}
}
// Get/Set for 2nd Radio button
public bool NeverTax
{
// If the Global Variable = 2 return true, else return false
get
{
if (glbTaxStatus.Equals(2))
{
return true;
}
else
{
return false;
}
}
// If the value being passed in = 2 set the Global Variable = 2, else do nothing
set
{
if (value.Equals(true))
{
glbTaxStatus = 2;
}
}
}
// Get/Set for 3rd Radio button
public bool AlwaysTax
{
// If the Global Variable = 3 return true, else return false
get
{
if (glbTaxStatus.Equals(3))
{
return true;
}
else
{
return false;
}
}
// If the value being passed in = 3 set the Global Variable = 3, else do nothing
set
{
if (value.Equals(true))
{
glbTaxStatus = 3;
}
}
}
// More code ...
Three seperate public variables with get/set accessing the same one global variable.
In the code behind, I have a function called during the Page_Load() setting all the controls databindings. For each radio button I add its own databinging.
radResale.DataBindings.Add("Checked", glbProductList, "Resale", true, DataSourceUpdateMode.OnPropertyChanged, false);
radNeverTax.DataBindings.Add("Checked", glbProductList, "NeverTax", true, DataSourceUpdateMode.OnPropertyChanged, false);
radAlwaysTax.DataBindings.Add("Checked", glbProductList, "Always", true, DataSourceUpdateMode.OnPropertyChanged, false);
I hope this helps someone!!
I think I would use my own GroupBox. I would bind the CustomGroupBox to your Model and set the correct RadioButton (using the tag or name properties) from the binded value.
This is my approach for binding a list of radio buttons to an enum.
Using the Enum as a string in the button's Tag property, I use the Binding.Format and Binding.Parse event to decide which button should be checked.
public enum OptionEnum
{
Option1 = 0,
Option2
}
OptionEnum _rbEnum = OptionEnum.Option1;
OptionEnum PropertyRBEnum
{
get { return _rbEnum; }
set
{
_rbEnum = value;
RaisePropertyChanged("PropertyRBEnum");
}
}
public static void FormatSelectedEnum<T>(object sender, ConvertEventArgs args) where T : struct
{
Binding binding = (sender as Binding);
if (binding == null) return;
Control button = binding.Control;
if (button == null || args.DesiredType != typeof(Boolean)) return;
T value = (T)args.Value;
T controlValue;
if (Enum.TryParse(button.Tag.ToString(), out controlValue))
{
args.Value = value.Equals(controlValue);
}
else
{
Exception ex = new Exception("String not found in Enum");
ex.Data.Add("Tag", button.Tag);
throw ex;
}
}
public static void ParseSelectedEnum<T>(object sender, ConvertEventArgs args) where T : struct
{
Binding binding = (sender as Binding);
if (binding == null) return;
Control button = binding.Control;
bool value = (bool)args.Value;
if (button == null || value != true) return;
T controlValue;
if (Enum.TryParse(button.Tag.ToString(), out controlValue))
{
args.Value = controlValue;
}
else
{
Exception ex = new Exception("String not found in Enum");
ex.Data.Add("Tag", button.Tag);
throw ex;
}
}
Then setup your data binding like this:
radioButton1.Tag = "Option1";
radioButton2.Tag = "Option2";
foreach (RadioButtonUx rb in new RadioButtonUx[] { radioButton1, radioButton2 })
{
Binding b = new Binding("Checked", this, "PropertyRBEnum");
b.Format += FormatSelectedRadioButton<OptionEnum>;
b.Parse += ParseSelectedRadioButton<OptionEnum>;
rb.DataBindings.Add(b);
}
I started to resolve the same problematic.
I used a RadioButtonBinding class which encapsulates all radiobuttons about an enum in the data source.
This following class keeps all radio buttons in a list and makes a lookup for the enum :
class RadioButtonBinding : ILookup<System.Enum, System.Windows.Forms.RadioButton>
{
private Type enumType;
private List<System.Windows.Forms.RadioButton> radioButtons;
private System.Windows.Forms.BindingSource bindingSource;
private string propertyName;
public RadioButtonBinding(Type myEnum, System.Windows.Forms.BindingSource bs, string propertyName)
{
this.enumType = myEnum;
this.radioButtons = new List<System.Windows.Forms.RadioButton>();
foreach (string name in System.Enum.GetNames(this.enumType))
{
System.Windows.Forms.RadioButton rb = new System.Windows.Forms.RadioButton();
rb.Text = name;
this.radioButtons.Add(rb);
rb.CheckedChanged += new EventHandler(rb_CheckedChanged);
}
this.bindingSource = bs;
this.propertyName = propertyName;
this.bindingSource.DataSourceChanged += new EventHandler(bindingSource_DataSourceChanged);
}
void bindingSource_DataSourceChanged(object sender, EventArgs e)
{
object obj = this.bindingSource.Current;
System.Enum item = obj.GetType().GetProperty(propertyName).GetValue(obj, new object[] { }) as System.Enum;
foreach (System.Enum value in System.Enum.GetValues(this.enumType))
{
if (this.Contains(value))
{
System.Windows.Forms.RadioButton rb = this[value].First();
if (value.Equals(item))
{
rb.Checked = true;
}
else
{
rb.Checked = false;
}
}
}
}
void rb_CheckedChanged(object sender, EventArgs e)
{
System.Windows.Forms.RadioButton rb = sender as System.Windows.Forms.RadioButton;
System.Enum val = null;
try
{
val = System.Enum.Parse(this.enumType, rb.Text) as System.Enum;
}
catch(Exception ex)
{
// cannot occurred if code is safe
System.Windows.Forms.MessageBox.Show("No enum value for this radio button : " + ex.ToString());
}
object obj = this.bindingSource.Current;
obj.GetType().GetProperty(propertyName).SetValue(obj, val, new object[] { });
this.bindingSource.CurrencyManager.Refresh();
}
public int Count
{
get
{
return System.Enum.GetNames(this.enumType).Count();
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.radioButtons.GetEnumerator();
}
public bool Contains(Enum key)
{
return System.Enum.GetNames(this.enumType).Contains(key.ToString());
}
public IEnumerable<System.Windows.Forms.RadioButton> this[Enum key]
{
get
{
return this.radioButtons.FindAll(a => { return a.Text == key.ToString(); });
}
}
IEnumerator<IGrouping<Enum, System.Windows.Forms.RadioButton>> IEnumerable<IGrouping<Enum, System.Windows.Forms.RadioButton>>.GetEnumerator()
{
throw new NotImplementedException();
}
public void AddControlsIntoGroupBox(System.Windows.Forms.GroupBox gb)
{
System.Windows.Forms.FlowLayoutPanel panel = new System.Windows.Forms.FlowLayoutPanel();
panel.Dock = System.Windows.Forms.DockStyle.Fill;
panel.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft;
foreach (System.Windows.Forms.RadioButton rb in this.radioButtons)
{
panel.Controls.Add(rb);
}
gb.Controls.Add(panel);
}
}
You are using the class into a form by adding that code in the constructor of the form:
public PageView()
{
InitializeComponent();
RadioButtonBinding rbWidth = new RadioButtonBinding(typeof(Library.EnumConstraint), this.pageBindingSource, "ConstraintWidth");
rbWidth.AddControlsIntoGroupBox(this.groupBox1);
RadioButtonBinding rbHeight = new RadioButtonBinding(typeof(Library.EnumConstraint), this.pageBindingSource, "ConstraintHeight");
rbHeight.AddControlsIntoGroupBox(this.groupBox3);
this.pageBindingSource.CurrentItemChanged += new EventHandler(pageBindingSource_CurrentItemChanged);
}
Set the tag name of your radio buttons to something that represents the value.
Create a string setting, for example, OptionDuplicateFiles, and give it the default value of the tag name for your default radio button.
To save your checked radio button:
Settings.Default.OptionDuplicateFiles = gbxDuplicateFiles.Controls
.OfType<RadioButton>()
.Where(b => b.Checked)
.Select(b => b.Tag)
.First()
.ToString();
To load your checked radio button:
(gbxDuplicateFiles.Controls
.OfType<RadioButton>()
.Where(b => b.Tag.ToString() == Settings.Default.OptionDuplicateFiles)
.First())
.Checked = true;
Tada!
I liked the idea of a RadioButtonGroupBox but I decided to create a version that is self supporting.
There is no reason to add value to Tag attribute or to introduce new value attributes.
Any assigned radio button is still a member of the RadioButtonGroupBox and the sequence of radiobuttons is defined during development.
Soo, I modified the code.
Now I can get and set the selected radiobutton by index position, By Control Name and by Text.
BTW Text is only useable if your asssigned Text is different for each radiobutton.
public class RadioButtonGroupBox : GroupBox
{
public event EventHandler SelectedChanged = delegate { };
int _nIndexPosCheckRadioButton = -1;
int _selected;
public int Selected
{
get
{
return _selected;
}
}
public int CheckedRadioButtonIndexPos
{
set
{
int nPosInList = -1;
foreach (RadioButton item in this.Controls.OfType<RadioButton>())
{
// There are RadioButtonItems in the list...
nPosInList++;
// Set the RB that should be checked
if (nPosInList == value)
{
item.Checked = true;
// We can stop with the loop
break;
}
}
_nIndexPosCheckRadioButton = nPosInList;
}
get
{
int nPosInList = -1;
int nPosCheckeItemInList = -1;
foreach (RadioButton item in this.Controls.OfType<RadioButton>())
{
// There are RadioButtonItems in the list...
nPosInList++;
// Find the RB that is checked
if (item.Checked)
{
nPosCheckeItemInList = nPosInList;
// We can stop with the loop
break;
}
}
_nIndexPosCheckRadioButton = nPosCheckeItemInList;
return _nIndexPosCheckRadioButton;
}
}
public string CheckedRadioButtonByText
{
set
{
int nPosInList = -1;
foreach (RadioButton item in this.Controls.OfType<RadioButton>())
{
// There are RadioButtonItems in the list...
nPosInList++;
// Set the RB that should be checked
if (item.Text == value)
{
item.Checked = true;
// We can stop with the loop
break;
}
}
_nIndexPosCheckRadioButton = nPosInList;
}
get
{
string cByTextValue = "__UNDEFINED__";
int nPosInList = -1;
int nPosCheckeItemInList = -1;
foreach (RadioButton item in this.Controls.OfType<RadioButton>())
{
// There are RadioButtonItems in the list...
nPosInList++;
// Find the RB that is checked
if (item.Checked)
{
cByTextValue = item.Text;
nPosCheckeItemInList = nPosInList;
// We can stop with the loop
break;
}
}
_nIndexPosCheckRadioButton = nPosCheckeItemInList;
return cByTextValue;
}
}
public string CheckedRadioButtonByName
{
set
{
int nPosInList = -1;
foreach (RadioButton item in this.Controls.OfType<RadioButton>())
{
// There are RadioButtonItems in the list...
nPosInList++;
// Set the RB that should be checked
if (item.Name == value)
{
item.Checked = true;
// We can stop with the loop
break;
}
}
_nIndexPosCheckRadioButton = nPosInList;
}
get
{
String cByNameValue = "__UNDEFINED__";
int nPosInList = -1;
int nPosCheckeItemInList = -1;
foreach (RadioButton item in this.Controls.OfType<RadioButton>())
{
// There are RadioButtonItems in the list...
nPosInList++;
// Find the RB that is checked
if (item.Checked)
{
cByNameValue = item.Name;
nPosCheckeItemInList = nPosInList;
// We can stop with the loop
break;
}
}
_nIndexPosCheckRadioButton = nPosCheckeItemInList;
return cByNameValue;
}
}
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
var radioButton = e.Control as RadioButton;
if (radioButton != null)
radioButton.CheckedChanged += radioButton_CheckedChanged;
}
void radioButton_CheckedChanged(object sender, EventArgs e)
{
_selected = CheckedRadioButtonIndexPos;
SelectedChanged(this, new EventArgs());
}
}
My approach is to put each radio button into its own panel before binding them to a boolean property:
public static Binding Bind<TObject>(this RadioButton control, object dataSource, string dataMember)
{
// Put the radio button into its own panel
Panel panel = new Panel();
control.Parent.Controls.Add(panel);
panel.Location = control.Location;
panel.Size = control.Size;
panel.Controls.Add(control);
control.Location = new Point(0, 0);
// Do the actual data binding
return control.DataBindings.Add("Checked", dataSource, dataMember);
}
I would like to make an observation about the code block that might be helpful to people reading these posts. The following code may not always work as expected due to it's structure.
try
{
val = System.Enum.Parse(this.enumType, rb.Text) as System.Enum;
}
catch(Exception ex)
{
// cannot occurred if code is safe
System.Windows.Forms.MessageBox.Show("No enum value for this radio button : " + ex.ToString());
}
object obj = this.bindingSource.Current;
obj.GetType().GetProperty(propertyName).SetValue(obj, val, new object[] {
}
);
this.bindingSource.CurrencyManager.Refresh();
If an error occurs in the try block, the catch block will be executed. The code will continue to execute after the catch block. Since there was no handling of binding source, the variables following the catch could end up in a indeterminate state and may throw another exception that may or may not be handled.
A better approach is as follows
try
{
val = System.Enum.Parse(this.enumType, rb.Text) as System.Enum;
object obj = this.bindingSource.Current;
obj.GetType().GetProperty(propertyName).SetValue(obj, val, new object[] { });
this.bindingSource.CurrencyManager.Refresh();
}
catch(EntityException ex)
{
// handle error
}
catch(Exception ex)
{
// cannot occurred if code is safe
System.Windows.Forms.MessageBox.Show("No enum value for this radio button : " + ex.ToString());
}
This allows the enum value error to be handled as well as other errors that may occur. However use the EntityException or variations of it before the Exception block (all decendents of Exception have to come first). One can get specific entity state information for a entity framework error by using the entity framework classes instead of the Exception base class. This can be helpful for debugging or providing clearer run time messages for the user.
When I setup try-catch blocks I like to view it as a "layer" on top of the code. I make decisions about the flow of the exceptions through out the program, their display to the user, and what ever cleanup is required to allow the program to continue working properly without objects in a indeterminate state that can cascade to other errors.
I liked Jan Hoogma GroupBox binding but preferred more control over values you could bind to started by creating RadioButtonBIndable which can have values of different types assigned to it which are strongly typed.
public class RadioButtonBindable : RadioButton
{
public Int32 ValueInt { get; set; }
public Decimal ValueDecimal { get; set; }
public String ValueString { get; set; }
public Boolean ValueBoolean { get; set; }
}
Then I can work on the GroupBox and create GroupBoxBindable that can be bound to any of the values in the RadioButtonBindable (you could bind to multiple values e.g. ValueInt = 23 and ValueString = "Example Text"
You can also set a default value in case of teh unlikeley event where there is no RadioButton selected.
public class RadioButtonGroupBoxBindable : GroupBox, INotifyPropertyChanged
{
//public event EventHandler SelectedChanged = delegate { };
public event EventHandler ValueIntChanged = delegate { };
public event EventHandler ValueDecimalChanged = delegate { };
public event EventHandler ValueStringChanged = delegate { };
public event EventHandler ValueBooleanChanged = delegate { };
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public Int32 DefaultValueInt { get; set; }
public Decimal DefaultValueDecimal { get; set; }
public String DefaultValueString { get; set; }
public Boolean DefaultValueBoolean { get; set; }
public Boolean ValueBoolean
{
set
{
foreach (Control control in this.Controls)
{
if (control is RadioButtonBindable)
{
RadioButtonBindable item = (RadioButtonBindable)control;
if (item.ValueBoolean == value)
{
item.Checked = true;
break;
}
}
}
}
get
{
Boolean retVal = DefaultValueBoolean;
foreach (Control control in this.Controls)
{
if (control is RadioButtonBindable)
{
RadioButtonBindable item = (RadioButtonBindable)control;
if (item.Checked)
{
retVal = item.ValueBoolean;
break;
}
}
}
return retVal;
}
}
public int ValueInt
{
set
{
foreach (Control control in this.Controls)
{
if (control is RadioButtonBindable)
{
RadioButtonBindable item = (RadioButtonBindable)control;
if (item.ValueInt == value)
{
item.Checked = true;
break;
}
}
}
}
get
{
int retVal = DefaultValueInt;
foreach (Control control in this.Controls)
{
if (control is RadioButtonBindable)
{
RadioButtonBindable item = (RadioButtonBindable)control;
if (item.Checked)
{
retVal = item.ValueInt;
break;
}
}
}
return retVal;
}
}
public decimal ValueDecimal
{
set
{
foreach (Control control in this.Controls)
{
if (control is RadioButtonBindable)
{
RadioButtonBindable item = (RadioButtonBindable)control;
if (item.ValueDecimal == value)
{
item.Checked = true;
break;
}
}
}
}
get
{
decimal retVal = DefaultValueDecimal;
foreach (Control control in this.Controls)
{
if (control is RadioButtonBindable)
{
RadioButtonBindable item = (RadioButtonBindable)control;
if (item.Checked)
{
retVal = item.ValueDecimal;
break;
}
}
}
return retVal;
}
}
public string ValueString
{
set
{
foreach (Control control in this.Controls)
{
if (control is RadioButtonBindable)
{
RadioButtonBindable item = (RadioButtonBindable)control;
if (item.ValueString == value)
{
item.Checked = true;
break;
}
}
}
}
get
{
string retVal = DefaultValueString;
foreach (Control control in this.Controls)
{
if (control is RadioButtonBindable)
{
RadioButtonBindable item = (RadioButtonBindable)control;
if (item.Checked)
{
retVal = item.ValueString;
break;
}
}
}
return retVal;
}
}
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
var radioButton = e.Control as RadioButtonBindable;
if (radioButton != null)
radioButton.CheckedChanged += radioButton_CheckedChanged;
}
void radioButton_CheckedChanged(object sender, EventArgs e)
{
if (((RadioButtonBindable)sender).Checked)
{
OnPropertyChanged("ValueInt");
OnPropertyChanged("ValueDecimal");
OnPropertyChanged("ValueString");
OnPropertyChanged("ValueBoolean");
ValueIntChanged(this, new EventArgs());
ValueDecimalChanged(this, new EventArgs());
ValueStringChanged(this, new EventArgs());
ValueBooleanChanged(this, new EventArgs());
}
}
}