Let's say I got few radiobuttons and some custom object as datasource.
As example
public enum SomeModeType
{
firstMode = 10,
secondMode = 20,
thirdMode = 30
}
public class MyCustomObject:INotifyPropertyChanged
{
private SomeModeType _mode;
public SomeModeType Mode
{
set { _mode = value; }
get { return _mode; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
How to bind this object property (if its possible) to 3 different radiobuttons with something like:
If radiobuttonOne checked - object property mode sets to firstMode
If radiobuttonTwo checked - object property mode sets to secondMode
If radiobuttonThree checked - object property mode sets to thirdMode
etc etc
Or its better to use events for this?
P.S.
I know how to use events but its overwhelmed to create event by event like rb1chnaged, rb2changed, ..., rb100changed, isnt it?
P.P.S.
MerryXmas!
For each value of the enum, you need to create a RadioButton and bind its Checked value to Mode property of data source. Then you need to use Format and Parse event of Binding to convert Mode value to suitable value for Checked property and vise versa.
Example - RadioButton List using FlowLayoutPanel
For example put a FlowLayoutPanel control on your form and then in Load event of Form write following code. The code will add RadioButton controls to the flow layout panel dynamically and performs data-binding:
var enumValues = Enum.GetValues(typeof(SomeModeType)).Cast<object>()
.Select(x => new { Value = x, Name = x.ToString() }).ToList();
enumValues.ForEach(x =>
{
var radio = new RadioButton() { Text = x.Name, Tag = x.Value };
var binding = radio.DataBindings.Add("Checked", dataSource,
"Mode", true, DataSourceUpdateMode.OnPropertyChanged);
binding.Format += (obj, ea) =>
{ ea.Value = ((Binding)obj).Control.Tag.Equals(ea.Value); };
binding.Parse += (obj, ea) =>
{ if ((bool)ea.Value == true) ea.Value = ((Binding)obj).Control.Tag; };
flowLayoutPanel1.Controls.Add(radio);
});
In above example, dataSource can be a MyCustomObject or a BindingList<MyCustomObject> or a BindingSource which contains a List<MyCustomObject> in its DataSource.
Another alternative - RadioButton List using Owner-draw ListBox
As another option you can use an owner-draw ListBox and render RadioButton for items. This way, you can bind SelectedValue of ListBox to Mode property of your object. The dataSourcs in following code can be like above example. Put a ListBox on form and write following code in Load event of form:
var enumValues = Enum.GetValues(typeof(SomeModeType)).Cast<object>()
.Select(x => new { Value = x, Name = x.ToString() }).ToList();
this.listBox1.DataSource = enumValues;
this.listBox1.ValueMember = "Value";
this.listBox1.DisplayMember = "Name";
this.listBox1.DataBindings.Add("SelectedValue", dataSource,
"Mode", true, DataSourceUpdateMode.OnPropertyChanged);
this.listBox1.DrawMode = DrawMode.OwnerDrawFixed;
this.listBox1.ItemHeight = RadioButtonRenderer.GetGlyphSize(
Graphics.FromHwnd(IntPtr.Zero),
RadioButtonState.CheckedNormal).Height + 4;
this.listBox1.DrawItem += (obj, ea) =>
{
var lb = (ListBox)obj;
ea.DrawBackground();
var text = lb.GetItemText(lb.Items[ea.Index]);
var r = ea.Bounds;
r.Offset(ea.Bounds.Height, 0);
RadioButtonRenderer.DrawRadioButton(ea.Graphics,
new Point(ea.Bounds.Location.X, ea.Bounds.Location.Y + 2), r, text,
lb.Font, TextFormatFlags.Left, false,
(ea.State & DrawItemState.Selected) == DrawItemState.Selected ?
RadioButtonState.CheckedNormal : RadioButtonState.UncheckedNormal);
};
Screenshot
You can see both solutions in following image:
var list = new List<MyCustomObject>() {
new MyCustomObject(){ Mode= SomeModeType.firstMode},
new MyCustomObject(){ Mode= SomeModeType.secondMode},
new MyCustomObject(){ Mode= SomeModeType.thirdMode},
};
this.myCustomObjectBindingSource.DataSource = list;
var dataSource = myCustomObjectBindingSource;
Note
After answering this question, I created and shared a RadioButtonList control in this post: WinForms RadioButtonList doesn't exist.
It has data-binding support and you can use this control like a ListBox. To do so, it's enough to bind it to the property of your model, and then set the data-source of the control simply this way:
radioButtonList1.DataSource = Enum.GetValues(typeof(YourEnumType));
Related
I want to create a bunch of simple checkboxes for a Windows from and have them change bools. Because it is a lot of them, I thought I could skip writing a bunch of CheckedChanged handlers and have a generic "Creator" function like this
static CheckBox CreateCheckBox(ref bool binding, string name)
{
var box = new CheckBox();
box.Checked = binding;
box.CheckedChanged += (object sender, EventArgs e) => { binding = !binding; };
box.Text = name;
return box;
}
And Then create my boxes like:
ParentForm.Controls.Add(CreateCheckBox(ref prop1, nameof(prop1));
ParentForm.Controls.Add(CreateCheckBox(ref prop2, nameof(prop2));
.....
I obviously got the "Can't use ref in a lambda expression or anonymous function". I read up on it and it makes sense.
But is there a way to have this sort of simple creator function that that generically adds the handlers?
I know there would be an issue here with never removing the handler but this is for a debug mode feature that is only used by developers to debug and improve very specific.algorithm.
Maybe I am just being lazy to write and add all the handlers but I also just felt cleaner.
You do not need to pass anything by reference, use one of the following options:
Pass a getter Func and a setter Action
Setup databinding
Example 1 - Pass a getter Func or a setter Action
Pass the getter and setter as func/action:
public CheckBox CreateCheckBox(string name, Func<bool> get, Action<bool> set)
{
var c = new CheckBox();
c.Name = name;
c.Text = name;
c.Checked = get();
c.CheckedChanged += (obj, args) => set(!get());
return c;
}
And use it like this, for example:
CreateCheckBox("checkBox1", () => textBox1.Enabled, (x) => textBox1.Enabled = x);
CreateCheckBox("checkBox2", () => textBox2.Enabled, (x) => textBox2.Enabled = x);
Example 2 - Setup databinding
What you are trying to implement is what you can achieve by setting up data binding:
public CheckBox CreateCheckBox(string name, object dataSource, string dataMember)
{
var c = this.Controls[name] as CheckBox;
c.Name = name;
c.Text = name;
c.DataBindings.Add("Checked", dataSource,
dataMember, false, DataSourceUpdateMode.OnPropertyChanged);
return c;
}
And use it like this:
CreateCheckBox("checkBox1", textBox1, "Enabled");
CreateCheckBox("checkBox2", textBox2, "Enabled");
I have been working with these properties for a while and have never had this problem before. I have a property like this:
public List<AirlineTickets_DOL> lstAirlineTickets
{
get
{
if (!(ViewState["lstAirlineTickets"] is List<AirlineTickets_DOL>))
{
ViewState["lstAirlineTickets"] = new List<AirlineTickets_DOL>();
}
return (List<AirlineTickets_DOL>)ViewState["lstAirlineTickets"];
}
set
{
ViewState["lstAirlineTickets"] = (List<AirlineTickets_DOL>)value;
}
}
When the data is returned somehow inside the OnTextChanged event I have to fill it in as shown below :
protected void tbFlightNumber_Book_Ticket_TextChanged(object sender, EventArgs e)
{
AutoFillAirlineTicket(sender);
}
private void AutoFillAirlineTicket(object sender)
{
TextBox tbFlightNumber_Book_Ticket = ((Control)sender).NamingContainer.FindControl("tbFlightNumber_Book_Ticket") as TextBox;
TextBox tbFrom_Book_Ticket = ((Control)sender).NamingContainer.FindControl("tbFrom_Book_Ticket") as TextBox;
TextBox tbTo_Book_Ticket = ((Control)sender).NamingContainer.FindControl("tbTo_Book_Ticket") as TextBox;
FillFlightData(tbFlightNumber_Book_Ticket, tbDate_Book_Ticket, tbFrom_Book_Ticket, tbTo_Book_Ticket);
UpdatePanel upAirlineTicket = ((Control)sender).NamingContainer.FindControl("upAirlineTicket") as UpdatePanel;
upAirlineTicket.Update();
//List<AirlineTickets_DOL> lstAirlineTickets = new List<AirlineTickets_DOL>();
lstAirlineTickets.Add(new AirlineTickets_DOL
{
counter = (nCounter > 0 ? nCounter : 1),
FlightNumber = tbFlightNumber_Book_Ticket.Text,
From = tbFrom_Book_Ticket.Text,
To = tbTo_Book_Ticket.Text,
});
nCounter++;
ListView lstviewAirlineTickets = ((Control)sender).NamingContainer.FindControl("lstviewAirlineTickets") as ListView;
lstviewAirlineTickets.DataSource = lstAirlineTickets;
lstviewAirlineTickets.DataBind();
}
When I remove the comment, the FillFlightData function fills the controls (TextBoxes), but when using the property as I explained above, the process of filling the fields does not work and does not give any output on the browser.
If you need more explain just tell me. I will be happy if someone help.
I found the solution, I changed the event to normal onClick Event and make sure the class had to be defined as [Serializable].
I am using behavior for ComboBox described in How can I make a WPF combo box have the width of its widest element in XAML?
unlike in question I'm creating ComboBox (for toolbar) in code behind:
private static ComboBox GetCombobox(ToolbarItemViewModel item)
{
var cmbBox = new ComboBox();
cmbBox.Name = item.Name;
item.CmbBoxItems = new ObservableCollection<KeyValuePair<string, string>>(NisDllInterface.GetComboBoxValues(NisDllInterface.MainFrameName, item.Name));
Binding itemsBinding = new Binding("CmbBoxItems");
itemsBinding.Source = item;
cmbBox.SetBinding(ComboBox.ItemsSourceProperty, itemsBinding);
cmbBox.DisplayMemberPath = "Value";
Binding selItemBinding = new Binding("SelectedItem");
selItemBinding.Source = item;
cmbBox.SetBinding(ComboBox.SelectedItemProperty, selItemBinding);
return cmbBox;
}
I get the example somewhat working by adding Loaded event handler in method above:
cmbBox.Loaded += (sender, args) =>
{
ComboBox comboBox = sender as ComboBox;
Action action = () => { comboBox.SetWidthFromItems(); };
comboBox.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
};
However I would like to know how can I attach behavior in code behind in same way it's done in XAML:
<ComboBox behaviors:ComboBoxWidthFromItemsBehavior.ComboBoxWidthFromItems="True">
There must be a method called something like
ComboBoxWidthFromItemsBehavior.SetComboBoxWidthFromItems(control, bool)
which you may use.
Scenario
I have a custom combo box where i have a label in the Combobox selection box. I need to change the label as I noted in the second image. But I want to do it only when I select items by selecting the check box. I can select multiple items, so the label should be updated as a comma separated value of selected items. If there is not enough space to display the full text of the label there should be "..." symbol to indicate that there are more items selected in the combo box.
I created a custom Label by inheriting the text Box control where I do all the changes in the callback event of a Dependency property. (Check custom Text Box code)
Now the problem is that the callback event in the custom Text box control is not firing when I change the bounded property in the View model (I am doing this by adding values to the observable collection in the code behind in check box on Check event. Please Check check box event code).
I can see that first time when I load default data in the view model the line is hit by the break point in the "Getter" part of "SelectedFilterResources" . But I never get a hit in the Setter part of the property.
Custom Text Box
The custom text box has the "CaptionCollectionChanged" callback event. It is the place where I have all logic to achieve my scenario. "Resources item" here is a type of Model.
public class ResourceSelectionBoxLable : TextBox
{
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
IsReadOnly = true;
}
public static List<ResourceItem> LocalFilterdResources = new List<ResourceItem>();
#region Dependancy Properties
public static readonly DependencyProperty FilterdResourcesProperty =
DependencyProperty.Register("SelectedFilterdResources",
typeof (ObservableCollection<ResourceItem>),
typeof (ResourceSelectionBoxLable),
new PropertyMetadata(new ObservableCollection<ResourceItem>(),
CaptionCollectionChanged));
public ObservableCollection<ResourceItem> SelectedFilterdResources
{
get
{
return
(ObservableCollection<ResourceItem>) GetValue(FilterdResourcesProperty);
}
set
{
SetValue(FilterdResourcesProperty, value);
LocalFilterdResources = new List<ResourceItem>(SelectedFilterdResources);
}
}
#endregion
private static void CaptionCollectionChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var resourceSelectionBoxLable = d as ResourceSelectionBoxLable;
if (resourceSelectionBoxLable != null)
{
if (LocalFilterdResources.Count <= 0)
{
resourceSelectionBoxLable.Text = "Resources"
}
else
{
var actualwidthOflable = resourceSelectionBoxLable.ActualWidth;
var newValue = e.NewValue as string;
//Get the Wdith of the Text in Lable
TextBlock txtMeasure = new TextBlock();
txtMeasure.FontSize = resourceSelectionBoxLable.FontSize;
txtMeasure.Text = newValue;
double textwidth = txtMeasure.ActualWidth;
//True if Text reach the Limit
if (textwidth > actualwidthOflable)
{
var appendedString = string.Join(", ",
LocalFilterdResources.Select(item => item.ResourceCaption)
.ToArray());
resourceSelectionBoxLable.Text = appendedString;
}
else
{
if (LocalFilterdResources != null)
{
var morestring = string.Join(", ",
(LocalFilterdResources as IEnumerable<ResourceItem>).Select(item => item.ResourceCaption)
.ToArray());
var subsring = morestring.Substring(0, Convert.ToInt32(actualwidthOflable) - 4);
resourceSelectionBoxLable.Text = subsring + "...";
}
}
}
}
}
}
Custom Combo Box.
This is the control where I use the above custom label. This is also a custom control so most of the properties and styles in this controls are custom made. "DPItemSlectionBoxTemplate" is a dependency property where I enable the Selection Box of the combo box by adding an attached property to the control template. This control works fine, because I use this control in other places in my system for different purposes.
<styles:CommonMultiComboBox
x:Name="Resourcescmb" IsEnabled="{Binding IsResourceComboEnable,Mode=TwoWay}"
IsTabStop="False"
>
<styles:CommonMultiComboBox.ItemDataTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelect, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Click="CheckBox_Click"
Content="{Binding ResourceCaption}"
Style="{StaticResource CommonCheckBoxStyle}"
Tag ="{Binding}"
Checked="Resource_ToggleButton_OnChecked" />
</DataTemplate>
</styles:CommonMultiComboBox.ItemDataTemplate>
<styles:CommonMultiComboBox.DPItemSlectionBoxTemplate>
<DataTemplate>
<filtersTemplate:ResourceSelectionBoxLable
Padding="0"
Height="15"
FontSize="10"
SelectedFilterdResources="{Binding DataContext.FilterdResources,ElementName=root ,Mode=TwoWay}" />
</DataTemplate>
</styles:CommonMultiComboBox.DPItemSlectionBoxTemplate>
</styles:CommonMultiComboBox>
ViewModel
private ObservableCollection<ResourceItem> _resourceItems;
public ObservableCollection<ResourceItem> FilterdResources
{
get { return _resourceItems; }
set
{
SetOnChanged(value, ref _resourceItems, "FilterdResources");
}
}
Constructor of View Model
FilterdResources=new ObservableCollection<ResourceItem>();
"SetOnChanged" is a method in the View Model base class where we have the INotifyPropertichanged implementation.
Check Box Event
private void Resource_ToggleButton_OnChecked(object sender, RoutedEventArgs e)
{
var senderControl = sender as CheckBox;
if(senderControl==null)
return;
var selectedContent=senderControl.Tag as ResourceItem;
if (selectedContent != null)
{
ViewModel.FilterdResources.Add(selectedContent);
}
}
I can access the View Model from the Code behind through the View Model Property.
Why is the call back event not notified when I change bounded values? Am i missing something here? Dependency properties are supposed to work for two way bindings aren't they? Could any one please help me on this?
Thanks in advance.
Looks like your issue is that you're expecting the CaptionCollectionChanged event to fire when the bound collection is changed (i.e. items added or removed). When in fact this event will fire only when you're changing an instance of the bound object.
What you need to do here is to subscribe to ObservableCollection's CollectionChanged event in the setter or change callback (which you already have - CaptionCollectionChanged) of your dependency property.
public static readonly DependencyProperty FilterdResourcesProperty =
DependencyProperty.Register("SelectedFilterdResources",
typeof (ObservableCollection<ResourceItem>),
typeof (ResourceSelectionBoxLable),
new PropertyMetadata(new ObservableCollection<ResourceItem>(),
CaptionCollectionChanged));
private static void CaptionCollectionChanged(DependencyObject d,
DependencyPropertyChangedEventArgs args)
{
var collection = args.NewValue as INotifyCollectionChanged;
if (collection != null)
{
var sender = d as ResourceSelectionBoxLable;
if (sender != null)
{
collection.CollectionChanged += sender.BoundItems_CollectionChanged;
}
}
}
private void BoundItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Do your control logic here.
}
Don't forget to add cleanup logic - unsubscribe from collection change when collection instance is changed and so on.
I am trying to create ASP.NET server control (pure code, without ascx template - because control must be completly contained in .dll and it must not rely on external .ascx files), and I have a problem with dynamically adding items to repeater.
I want to add item to repeater in reaction to SelectedIndexChanged event, but when i do second DataBind() in that event, i lose data from ViewModel (for example, textboxes contains default data instead of text entered by user).
Simplified version of my code (in large portion borrowed from MS composite control example - http://msdn.microsoft.com/en-us/library/3257x3ea%28v=vs.100%29.aspx):
[ToolboxData("<{0}:FilterControl runat=server />")]
public class FilterControl : CompositeControl, IPostBackDataHandler
{
private List<FilteringProperty> elements = new List<FilteringProperty>();
private DropDownList filteringElementsDropDownList;
private Repeater usedFiltersRepeater;
[Bindable(true), DefaultValue(null), Description("Active filters")]
public List<FilteringProperty> UsedElements
{
get
{
EnsureChildControls();
if (ViewState["UsedElements"] == null)
{
ViewState["UsedElements"] = new List<FilteringProperty>();
}
return (List<FilteringProperty>)ViewState["UsedElements"];
}
set
{
EnsureChildControls();
ViewState["UsedElements"] = value;
}
}
protected override void RecreateChildControls()
{
EnsureChildControls();
}
protected override void CreateChildControls()
{
Controls.Clear();
filteringElementsDropDownList = new DropDownList { AutoPostBack = true };
usedFiltersRepeater = new Repeater();
foreach (var element in elements)
{
filteringElementsDropDownList.Items.Add(new ListItem(element.DisplayName));
}
filteringElementsDropDownList.SelectedIndexChanged += (sender, e) =>
{
string selectedText = filteringElementsDropDownList.SelectedValue;
FilteringProperty condition = elements.First(x => x.DisplayName == selectedText);
var toRemove = filteringElementsDropDownList.Items.Cast<ListItem>().FirstOrDefault(x => x.Text == condition.DisplayName);
if (toRemove != null)
{
filteringElementsDropDownList.Items.Remove(toRemove);
}
UsedElements.Add(condition);
// ======> A <========
};
usedFiltersRepeater.ItemDataBound += (sender, args) =>
{
FilteringProperty dataItem = (FilteringProperty)args.Item.DataItem;
Control template = args.Item.Controls[0];
TextBox control = (TextBox)template.FindControl("conditionControl");
control.Text = dataItem.DisplayName;
// ======> C <========
};
usedFiltersRepeater.ItemTemplate = // item template
usedFiltersRepeater.DataSource = UsedElements;
usedFiltersRepeater.DataBind();
// ======> B <========
Controls.Add(filteringElementsDropDownList);
Controls.Add(usedFiltersRepeater);
}
}
I marked important portions of code with (A), (B) and (C)
The problem is, (A) is executed after DataBinding (B and C), so changes in UsedElements are not visible until next postback.
It is possible to add usedFiltersRepeater.DataBind(); after (A), but than all controls are recreated without data from viewstate (i.e empty)
Is there a way to dynamically change repeater after databinding, such that data of contained controls is preserved?
Tl;dr - i have a DropDownList and I want to add editable items to Repeater on SelectedIndexChanged (without losing viewstate).
I finally solved my problem.
My solution is rather dirty, but it seems to work fine.
Instead of simple databinding:
I get state from all controls in repeater and save it in temporary variable (state for each control includes everything, such as selected index for dropdownlists) using my function GetState()
modify this state in any way i want
restore full state using my function SetState()
For example:
FilterState state = GetState();
state.Conditions.Add(new ConditionState { Item = condition });
SetState(state);