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");
Related
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.
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));
Using WPF C#
Have a TimePicker from Xceed.WPF.Toolkit called TimePicker, want to be able to filter the files that are loaded to a listbox based on the last write time of a file
Question: I would like to limit the files listed based on the logTime variable using Linq and where the files have to match the logTime
Code for population of list box currently
private void LoadLogsNoDate(string ldate, string ext)
{
string[] logs = Directory.GetFiles(logPath + ldate, ext);
InitializeComponent();
logList = new ObservableCollection<String>();
logList.Clear();
lbLogs.ItemsSource = logList;
foreach (string logName in logs)
{
string s = logName.Substring(logName.IndexOf(ldate) + ldate.Length + 1);
int extPos = s.LastIndexOf("."); // <- finds the extension
s = s.Substring(0, extPos); // <- removes the extension
s = s.ToUpper(); // <- converts to uppercase
logList.Add(s); // <- adds the items it finds
}
DataContext = this;
}
Need to set the TimePicker value to a logTime variable then use the logTime to filter the list of items that are displayed
Successfully have used this code to get the LastWriteAccess time just need some assistance putting it all together properly
public static void Times(string sFile)
{
FileInfo info = new FileInfo(sFile);
DateTime time = info.LastWriteTime;
string s = time.ToString("HH:mm tt");
Console.WriteLine("Last Access: " + s);
}
First off, you will want to associate the time with the string representing the log file. Instead of an ObservableCollection you'll have an ObservableCollection. Log would be defined as:
public class Log
{
String LogName;
DateTime LogWriteTime;
}
Creating the collection would call your other function (modified to return the read time):
foreach (string logName in logs)
{
string s = logName.Substring(logName.IndexOf(ldate) + ldate.Length + 1);
int extPos = s.LastIndexOf("."); // <- finds the extension
s = s.Substring(0, extPos); // <- removes the extension
s = s.ToUpper(); // <- converts to uppercase
Log newItem = new Log();
newItem.LogName = s;
newItem.LogWriteTime = GetFileAccessTime(s)
logList.Add(s); // <- adds the items it finds
}
public DateTime GetFileAccessTime(string sFile)
{
FileInfo info = new FileInfo(sFile);
return info.LastWriteTime;
}
Now that we have our time stored off, I'm going to assume your TimePicker's SelectedValue property is bound to FilterTime. There are two ways to approach the problem of filtering:
Bind your view to a separate IEnumerable FilteredLogs and do the following in the setter of the FilterTime variable:
FilterdLogs = logList.Where(l => l.LogWriteTime >= FilterTime);
Use a CollectionViewSource. This method is awesome! First, create a CollectionViewSource property called LogsSource. Change your XAML to bind to this instead of the old ObservableCollection.
<ListBox ItemsSource="{Binding FilteredLogs.View}"/>
Now, in whatever you have for an Init function (constructor, whatever calls LoadLogsNoData, etc.) write:
FilteredLogs = new CollectionViewSource();
FilteredLogs.Source = logList;
FilteredLogs.Filter += CheckAccessTime;
This sets up a new CollectionViewSource that points to your logList collection, and uses the CheckAccessTime function to determine if a particular log entry should be included in the "View" property (which you previously bound to).
The CheckAccessTime function will look like:
private void CheckAccessTime(object sender, FilterEventArgs e)
{
Log logEntry = e.Item as Log;
if (logEntry != null)
{
if (logEntry.LogWriteTime >= FilterTime)
{
e.Accepted = true;
}
else
{
e.Accepted = false;
}
}
}
Finally, you need to refresh the filter anytime the selection changes. So, in the setter of FilterTime write:
FilteredLogs.View.Refresh();
The second option is, in my opinion, a much cleaner way of accomplishing the task, though it may be a bit more confusing at first. Let me me know if I can clarify anything!
This blog post was greatly helpful in researching the second method: http://uicraftsman.com/blog/2010/10/27/filtering-data-using-collectionviewsource/
MSDN for the filter event: MSDN
So I have an event that can be triggered by a change of the checked value of some checkboxes and some radiobuttons. I want to get the Checked value of the control that triggered the event. I know that if it is a checkbox I can do something like bool checkedValue = (sender as CheckBox).Checked;
(and same for radiobuttons). but what if I don't know the type of the control? Is there a class for both of them?
The best approach would be to use an interface, but according to your comment on Felice Pollano's answer you don't want to.
I would then suggest to look for the type at runtime:
bool checked;
if (sender is CheckBox)
checked = ((CheckBox)sender).Checked;
if (sender is RadioButton)
checked = ((RadioButton)sender).Checked;
If you are running .NET 4 you might want to try this ugly trick:
private void checkBox1_Click(dynamic sender, EventArgs e) {
bool isChecked = sender.Checked;
textBox1.Text = isChecked ? "Checked" : "Unchecked";
}
not very type safe, but fun!
You may build a small function taking a control parameter. Then try to cast first to Check box, if not null use the Checked result. If null, try the same with radio button. If that's null too, throw an InvalidParameterException or something appropriate. Else return the value you saved.
bool GetChecked(object ctrl) {
bool result = false;
CheckBox cb = ctrl as CheckBox;
if ( null == cb ) {
RadioButton rb = ctrl as RadioButton;
if ( null == rb ) {
throw new Exception ( "ctrl is of the wrong type " );
}
result = rb.Checked;
} else {
result = cb.Checked;
}
return result;
}
Not tried to compile, just to provide the idea. Second idea: do a bit reflection, look if there's a property named Checked and get the value. Rough, untested Code:
bool GetChecked(object ctrl) {
bool result = false;
Type reflectedResult = ctrl.GetType();
PropertyInfo[] properties = reflectedResult.GetProperties();
List<System.Reflection.PropertyInfo> properties = ctrl.GetProperties ().Where ( itm => itm.Name == "Checked" ).ToList ();
if ( properties.Count == 1 )
{
bool result = (bool)properties[0].GetValue ( ctrl, null );
} else {
throw new Exception ( "ctrl is of the wrong type " );
}
return result;
}
Though I don't like the code of both...
They both derive from ButtonBase but ButtonBase does not implement IsChecked. As a suggestion create two subclass of CheckBox and RadioButton, declare an Interface, ie IHasCheck, implement it properly in your derived classes, and try to cast at this interface in the event handler:
public interface ICheckable
{
bool Checked { get; set; }
}
class RadioButtonExtended : RadioButton,ICheckable
{
}
class CheckBoxExtended : CheckBox, ICheckable
{
}
i wish to create a form at runtime that will read the columns for any datasource and create fields based on the columns and datatype just like a datagridviews insert line
Best regards,
Mark
What you are doing sounds a lot like how PropertyGrid already works, which is essentially:
foreach(PropertyDescriptor prop in TypeDescriptor.GetProperties(obj)) {
object val = prop.GetValue(obj);
string s = prop.Converter.ConvertToString(val);
Control cont = // TODO: create some control and set x/y
cont.Text = s;
this.Controls.Add(cont);
}
To avoid lots of work with alignment, using Dock to set the positions might help:
using(Form form = new Form())
using (PropertyGrid grid = new PropertyGrid())
{
form.Text = obj.ToString(); // why not...
grid.Dock = DockStyle.Fill;
form.Controls.Add(grid);
grid.SelectedObject = obj;
form.ShowDialog(this);
}
I wonder if it is easier to use PropertyGrid in simple circumstances, though. Or there are some 3rd-party versions that work similarly.
Ok so heres what i came up with!
public partial class Form2 : Form
{
private Boolean isBrowsable(PropertyInfo info)
{
return info.GetCustomAttributes(typeof(BrowsableAttribute), false).Length>-1;
}
public Form2()
{
InitializeComponent();
}
public Form2(Boolean showCheckBoxes)
{
InitializeComponent();
_showCheckBoxes = true;
}
private Boolean _showCheckBoxes;
private Object _reflection;
private TableLayoutPanel _table = new TableLayoutPanel{Dock=DockStyle.Fill, CellBorderStyle = TableLayoutPanelCellBorderStyle.Single};
public Object SelectedObject
{
get
{
return _reflection;
}
set
{
//clear all controls from the table
_table.Controls.Clear();
foreach (var property in _reflection.GetType().GetProperties())
{
if (isBrowsable(property))
{
if ((property.PropertyType == typeof(int)) || (property.PropertyType == typeof(string)))
{
var textField = new TextBox { Dock = DockStyle.Fill, AutoSize = true };
textField.DataBindings.Add("Text", _reflection, property.Name);
_table.Controls.Add(textField, 2, _table.RowCount += 1);
var propertyLabel = new Label
{
Text = property.Name,
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft
};
_table.Controls.Add(propertyLabel, 1, _table.RowCount);
if (_showCheckBoxes)
{
var checkBox = new CheckBox
{
AutoSize = true,
Name = property.Name,
Dock = DockStyle.Left,
CheckAlign = ContentAlignment.TopLeft
};
_table.Controls.Add(checkBox, 0, _table.RowCount);
}
}
}
}
//add one extra row to finish alignment
var panel = new Panel { AutoSize = true };
_table.Controls.Add(panel, 2, _table.RowCount += 1);
_table.Controls.Add(panel, 1, _table.RowCount);
if (_showCheckBoxes)
{
_table.Controls.Add(panel, 0, _table.RowCount);
}
Controls.Add(_table);
if (!Controls.Contains(_table))
Controls.Add(_table);
}
}
public Boolean Execute(Object reflection)
{
SelectedObject = reflection;
return ShowDialog() == DialogResult.OK;
}
}
thanks all!
I don't fully understand your question. Is it correct that you want to create a Windows form which provides input fields (textboxes, checkboxes, etc.) for all fields/properties of an object that you feed to the form as its DataSource?
You might have to use reflection for this (see the System.Reflection namespace). For example, to get a list of all properties:
using System.Reflection;
....
public object DataSource;
...
Debug.Assert( DataSource != null );
var properties = DataSource.GetType().GetProperties();
You would then instantiate one input control per property:
foreach ( var property in properties )
{
// extract some information about each property:
string propertyName = property.Name;
Type propertyType = property.PropertyType;
bool propertyReadOnly = !property.CanWrite;
// create input controls based on this information:
// ...
}
However, it might be fairly tricky to reliably map property types to the correct input control; for example, what are you going to do when you encounter a property with some unknown class as its type, or when a property is a collection of values? You might have to create a sub-form inside your form in some cases; in other cases, a listbox might be enough.
I've recently built a sample project that uses the Dynamic Data assemblies of ASP.NET to do just this for a WPF grid, but I'm sure you could adapt the concept to WinForms. Dynamic Data provides much richer metadata than just reflection or the database, but it does require an Entity Data Model, or a LINQ to SQL data model.
basically, all you need is a reference to System.Web.DymamicData, and maybe you can find something useful in my class:
public class DynamicDataGridBuilder<TContext, TEntity> where TEntity : EntityObject
{
readonly MetaModel model = new MetaModel();
public DynamicDataGridBuilder()
{
model.RegisterContext(typeof(TContext), new ContextConfiguration { ScaffoldAllTables = true });
}
public void BuildColumns(DataGrid targetGrid)
{
MetaTable metaTable = model.GetTable(typeof(TEntity));
// Decision whether to auto-generated columns still rests with the caller.
targetGrid.Columns.Clear();
foreach (var metaColumn in metaTable.Columns.Where(x => x.GetType().Name == "MetaColumn" && x.Scaffold))
{
switch (metaColumn.ColumnType.Name)
{
case "Boolean":
targetGrid.Columns.Add(new DataGridCheckBoxColumn { Binding = new Binding(metaColumn.Name), Header = metaColumn.DisplayName });
break;
default:
targetGrid.Columns.Add(new DynamicDataGridTextColumn { MetaColumn = metaColumn, Binding = new Binding(metaColumn.Name), Header = metaColumn.DisplayName });
break;
}
}
}
}
TContext is the type of your object model, and TEntity the type of the entity / class in that model your want to generate controls for.
Use control data binding. It will do all the work for you.