I have a WinForm with multiple controls that are bound to an management object. Once of the controls is a RadCheckedDropDownList that is bound to a single object property containing a bitwise value, something like this:
2, 'None'
4, 'Mornings'
8, 'Afternoon'
16, 'Evenings'
32, 'Anytime'
Using bitwise values I plan to store, say, 24 in the field if the user chooses Afternoons and Evenings. What I cannot figure out is how to bind this to the object property. if there a direct way to do this or will it need to be handled in the UI. Can I bind it as directly as I can, say, a string or in value that goes to a single property? Do you have sample code for such an approach?
Thanks
Carl
3 Answers, 1 is accepted
Hello, Carl,
I would recommend you to have a look at the following help article demonstrating how to properly bind the control:
The DataSource property specifies the data structure to be bound. The DisplayMember sets the particular field in the data source which will be used from the items in RadCheckedDropDownList for their Text. The ValueMember specifies the particular field in the data source which will be stored in the items Value property.
I hope this information helps. If you need any further assistance please don't hesitate to contact me.
Regards,
Dess | Tech Support Engineer, Principal
Progress Telerik
Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.
Hi, Carl,
If the user is allowed to make a single selection. a better option is to use RadDropDownList, not RadCheckedDropDownList.
Following the provided information, I have prepared a sample code snippet to simulate as close as possible the described scenario. The achieved result is illustrated in the gif file:
public RadForm1()
{
InitializeComponent();
DataTable dt = new DataTable();
dt.Columns.Add("Id", typeof(int));
dt.Columns.Add("Name", typeof(string));
dt.Rows.Add(2, "None");
dt.Rows.Add(4, "Mornings");
dt.Rows.Add(8, "Afternoon");
dt.Rows.Add(16, "Evenings");
dt.Rows.Add(32, "Anytime");
this.radDropDownList1.DataSource = dt;
this.radDropDownList1.DisplayMember = "Name";
this.radDropDownList1.ValueMember = "Id";
AccountManager manager= new AccountManager(123,"Telerik Manager", 3);
this.radDropDownList1.DataBindings.Add("SelectedValue", manager, "PreferredLanguage",false, DataSourceUpdateMode.OnPropertyChanged );
this.radDataEntry1.DataSource = manager;
}
public class AccountManager : System.ComponentModel.INotifyPropertyChanged
{
int m_id;
string m_name;
int m_preferredLanguage ;
public event PropertyChangedEventHandler PropertyChanged;
public AccountManager(int m_id, string m_name, int m_preferredLanguage)
{
this.m_id = m_id;
this.m_name = m_name;
this.m_preferredLanguage = m_preferredLanguage;
}
public int Id
{
get
{
return m_id;
}
set
{
if (this.m_id != value)
{
this.m_id = value;
OnPropertyChanged("Id");
}
}
}
public string Name
{
get
{
return m_name;
}
set
{
if (this.m_name != value)
{
this.m_name = value;
OnPropertyChanged("Name");
}
}
}
public int PreferredLanguage
{
get
{
return m_preferredLanguage;
}
set
{
if (this.m_preferredLanguage != value)
{
this.m_preferredLanguage = value;
OnPropertyChanged("PreferredLanguage");
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I hope this would fit your scenario.
Regards,
Dess | Tech Support Engineer, Principal
Progress Telerik
Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.
Dess
Thanks but that's not what I was asking. Your example shows a single selection and I have this part working just fine. In my case, the user is allowed to make multiple selections in the dropdownlist. If they choose both Afternoon (value = 8) and Evenings (value = 16), the single value stored in the database is the sum of these two values, 24.
How can I bind the dropdownlist control to an object property that will receive the value 24 from the control when the user selects these two options? Also, how to pass this value back to the control so that when the form loads for editing, Afternoon and Evenings choices are already selected?
Thanks
Carl
Thank you, Carl, for specifying the additional information. This functionality is a subject custom logic and should be implemented by the developer according to the specific requirements. I can suggest a possible approach and create a custom property in RadCheckedDropDownList which considers the which items are checked and returns the sum of all Ids that are checked. Thus, the value (24) will be stored to the custom object. However, what logic should be followed for checking the items in the drop down when you have stored value 24 in the PreferredLanguage property?
I have modified the sample snippet ina way to demonstrate the approach you can choose for achieving the goal. It should be extended with the custom logic for determining which items to be checked having 24 value in the custom object :
public RadForm1()
{
InitializeComponent();
DataTable dt = new DataTable();
dt.Columns.Add("Id", typeof(int));
dt.Columns.Add("Name", typeof(string));
dt.Rows.Add(2, "None");
dt.Rows.Add(4, "Mornings");
dt.Rows.Add(8, "Afternoon");
dt.Rows.Add(16, "Evenings");
dt.Rows.Add(32, "Anytime");
this.radCheckedDropDownList1.DataSource = dt;
this.radCheckedDropDownList1.DisplayMember = "Name";
this.radCheckedDropDownList1.ValueMember = "Id";
AccountManager manager= new AccountManager(123,"Telerik Manager", 4);
this.radCheckedDropDownList1.DataBindings.Add("MyValue", manager, "PreferredLanguage",false, DataSourceUpdateMode.OnPropertyChanged );
this.radDataEntry1.DataSource = manager;
}
public class MyCheckedDropDownList : RadCheckedDropDownList
{
protected override void element_PopupClosed(object sender, RadPopupClosedEventArgs args)
{
base.element_PopupClosed(sender, args);
int sum = 0;
foreach (RadListDataItem item in this.CheckedItems)
{
DataRowView rowView = item.DataBoundItem as DataRowView;
sum += (int)rowView.Row["Id"];
}
this.MyValue = sum;
}
public int MyValue
{
get
{
int sum = 0;
foreach (RadListDataItem item in this.CheckedItems)
{
DataRowView rowView = item.DataBoundItem as DataRowView;
sum += (int)rowView.Row["Id"];
}
return sum;
}
set
{
int sumId = value;
foreach (RadCheckedListDataItem item in this.Items)
{
DataRowView rowView = item.DataBoundItem as DataRowView;
if (sumId == (int)rowView.Row["Id"])
{
item.Checked = true;
break;
}
}
//TODO
//check all relevant items according to the incoming Id
this.OnNotifyPropertyChanged("MyValue");
}
}
public override string ThemeClassName
{
get
{
return typeof(RadCheckedDropDownList).FullName;
}
}
}
Dess
My colleague and I worked on this problem and it turns out you don't need to subclass the control. Here is the solution:
Here are the flags:
[Flags] public enum FlagOptions { None = 0, Mornings = 1 << 0, Afternoon = 1 << 1, Evenings = 1 << 2 //Anytime = Mornings | Afternoon | Evenings }
In the form class declare a variable and bind to the control:
private FlagEnumToList<FlagOptions> enumToList;
//Do the binding
enumToList = new FlagEnumToList<FlagOptions>(ddlBestTimeToCall, () => accountManager.BestTimeToCall, (flag) => accountManager.BestTimeToCall = flag);
Here is the class to manage load the dropdownlist and manage the data:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
using Telerik.WinControls.UI;
using VisionDataAndServices.Common;
using static VisionUI.FormAccount;
namespace VisionUI.Common
{
public class ActualRecord
{
public FlagOptions BitMask { get; set; }
}
public sealed class FlagEnumToList<TEnum> : IDisposable where TEnum : struct, Enum
{
private readonly Func<TEnum> getPropertyValue;
private readonly Action<TEnum> setPropertyValue;
public record MultiSelect(string DisplayedValue, Predicate<TEnum> HasFlag, Action<TEnum, bool> SetFlag, TEnum EnumValue)
{
public bool IsChecked
{
get => HasFlag(EnumValue);
set => SetFlag(EnumValue, value);
}
}
private static readonly TEnum[] Values = Enum.GetValues<TEnum>();
private readonly BindingSource bindingSource;
public FlagEnumToList(RadCheckedDropDownList dropDownList, Func<TEnum> getPropertyValue, Action<TEnum> setPropertyValue)
{
this.getPropertyValue = getPropertyValue;
this.setPropertyValue = setPropertyValue;
var options = Values.Select(e => new MultiSelect(e.ToString(), HasFlag, SetFlag, e)).ToList();
var bindingList = new BindingList<MultiSelect>(options)
{
AllowEdit = false,
AllowNew = false,
AllowRemove = false,
RaiseListChangedEvents = true
};
bindingSource = new BindingSource
{
DataSource = bindingList,
RaiseListChangedEvents = true
};
ConfigureDisplayedList(dropDownList);
}
public void Refresh()
{
bindingSource.ResetItem(0);
}
private bool HasFlag(TEnum flag)
{
var value = getPropertyValue();
var intValue = ToInt(value);
var intFlag = ToInt(flag);
if (intValue == 0 && intFlag == 0) return true;
return intFlag != 0 && value.HasFlag(flag);
}
private void SetFlag(TEnum flag, bool isChecked)
{
var intFlag = ToInt(flag);
if (intFlag == 0 && isChecked)
{
setPropertyValue(ToEnum(0));
Refresh();
return;
}
var value = getPropertyValue();
var propertyValue = ToInt(value);
var multiSelectValue = ToInt(flag);
if (isChecked)
propertyValue |= multiSelectValue;
else
propertyValue ^= multiSelectValue;
var newValue = ToEnum(propertyValue);
setPropertyValue(newValue);
Refresh();
}
private void ConfigureDisplayedList(RadCheckedDropDownList dropDownList)
{
dropDownList.DataSource = bindingSource;
dropDownList.DisplayMember = nameof(MultiSelect.DisplayedValue);
dropDownList.ValueMember = nameof(MultiSelect.EnumValue);
dropDownList.CheckedMember = nameof(MultiSelect.IsChecked);
}
private static int ToInt(TEnum value)
{
return Convert.ToInt32(value);
}
private static TEnum ToEnum(int value)
{
return (TEnum)Enum.ToObject(typeof(TEnum), value);
}
public void Dispose()
{
bindingSource.Dispose();
}
}
}
We'd recommend either a knowledgebase article on how to work with bitwise flags or have the control handle this natively. DevExpress has this article which could serve as a guide: https://docs.devexpress.com/WindowsForms/DevExpress.XtraEditors.CheckedComboBoxEdit#populate-with-bit-fields-flags
Thanks
Carl
Hi, Carl,
I am glad that you have found a suitable approach for handling your scenario.
Thank you for sharing the knowledge base article. I would like to note that we already have a similar resource about a sample implementation of a FlagEnumEditor. Even though it is relevant for RadPropertyGrid, I believe that it can be used as guidance for covering other scenarios as well:
https://docs.telerik.com/devtools/winforms/knowledge-base/flagenumeditor-in-radpropertygrid
Dess
I saw that before I contacted you. That works for loading the control with options. What I'm looking to do is bind the users selections to a property. For example, In the code below I have a drop-down list that allows the user to make a single selection. Its loaded with a few items. The PreferredLanguage property of the accountManager object contains the values of the item in the database, say 3. This corresponds to French and so French will display in the control.
In my other control, I'll have a number like 24 which should check Afternoon and Evenings. How is this done? It may not be possible using this approach and if so I can handle it custom.
ddlPreferredLanguage.DataBindings.Add(nameof(ddlPreferredLanguage.SelectedValue), accountManager, nameof(accountManager.PreferredLanguage), false, DataSourceUpdateMode.OnPropertyChanged);
Thanks
Carl