Using IDataErrorInfo on related but non-mutually exclusive checkboxes - c#

I have a series of checkboxes on a form. One or more must be checked, and if not I want to display an error icon on them until one of them is.
My IDataErrorInfo implementation looks like so:
public string this[string columnName]
{
get
{
switch (columnName)
{
case "option1":
case "option2":
case "option3":
if (!this.option1 && !this.option2 && !this.option3)
return "Please select one or more of the 3 options";
}
}
}
Now, if none of the checkboxes that are bound to options1-3 are checked, each checkbox will have an error icon on them, which is fine, but when one of them IS checked, only that one checkbox will have its error icon removed (as opposed to all of them).
What's the ideal way of having the form re-poll validation for options1-3 when any one of them is changed?
If it helps (though I don't think it should be much different from normal winforms controls), I'm using DevExpress UI controls, so the checkboxes are CheckBoxEdit's and the ErrorProvider is the DxErrorProvider.
EDIT: SOLVED
I ended up manually notifying of property changed for the other options when one was changed.
private bool option1;
public bool Option1
{
get { return this.option1; }
set
{
this.option1 = value;
this.notifyPropertyChanged("Option1");
this.notifyPropertyChanged("Option2");
this.notifyPropertyChanged("Option3");
}
}
// repeat for options2-3

Related

C# WPF inotifypropertychanged interferes with data entry

I'm not sure if I'm asking this properly. I have a datagrid where column 1 data can come from two different places depending on a switch bit. The first place is a periodic update of the view model column from the model. The second place is if I manually edit a cell of column 1. I have a custom column binding in the XAML code binds to a list of classes. The problem is that when I start typing in data, it gets overwritten by the periodic update. Is there a proper way to design what I am looking to accomplish?
Below is the pseudo code for what is bound to Column 1 of the datagrid.
public int Column1
{
get
{
if(mySwitch)
return this.NumList[0].Data;
else
return this.NumList[0].EditedData;
}
set
{
if (mySwitch)
{
//this gets set every few seconds
this.Numlist[0].Data = value;
this.OnPropertyChanged("mydata");
}
else
{ //this gets called only when mySwitch is false and I'm in edit mode.
this.Numlist[0].EditedData = value;
this.OnPropertyChanged("myediteddata");
}
}
}
Thanks

Multiple Pages withing one Form? C#

I haven't done this for a while so am not quite sure how to do what I need, but I am sure it is pretty simple.
Basically, I have a form with a navigation pane. I want to make it so when a user clicks a button on that pane, say 'Home' it changes the content on the form, but doesn't actually switch to another form, if you get me?
As in, I would like the navigation pane to stay as it is the entire time and I only want the content of the form to change. It is almost like the 'TabControl' tool in Visual Studio's 'Toolbox' although instead of the tabs being directly above the content, I want them to be buttons displayed in a side pane. See the image below for a better understanding. Thanks!
(Side pane, and header stays the same regardless on what button is pressed, but the content changes.)
I'd implement this using UserControls. One UserControl is shown when a button is clicked. I'd create an interface (for example IView) that would be implemented by each UserControl that declares common functionality, like for example a method to check whether you can switch from one to another (like a form's OnClosing event) like this:
public interface IView
{
bool CanClose();
}
public UserControl View1: IView
{
public bool CanClose()
{
...
}
}
public UserControl View2: IView
{
public bool CanClose()
{
...
}
}
Then, switching views is quite easy:
private bool CanCurrentViewClose()
{
if (groupBox1.Controls.Count == 0)
return true;
IView v = groupBox1.Controls[0] as IView;
return v.CanClose();
}
private void SwitchView(IView newView)
{
if (groupBox1.Controls.Count > 0)
{
UserControl oldView = groupBox1.Controls[0] as UserControl;
groupBox1.Controls.Remove(oldView);
oldView.Dispose();
}
groupBox1.Controls.Add(newView);
newView.Dock = Dock.Fill;
}
In a button you could do this:
private void btnHome_Click(object sender, EventArgs e)
{
if (CanCurrentViewClose())
{
ViewHome v = new ViewHome();
// Further initialization of v here
SwitchView(v);
}
else
{
MessageBox.Show("Current View can not close!");
}
}
I've successfully used this approach on many occasions.
Simplest way is to place multiple Panels as content holders, implement content manager which keeps references to Panels and with it show/hide desired panel.
Simple, but for smaller apps it will work
You can simply use a TabControl which has as many TabPages as you want. For the TabControl you can set the Alignment property to Left

C# Dynamic form (reflection) - linking controls

Sorry for the poor quality of the title. I couldn't think of a better way to phrase this.
For a project I'm currently working on with a few friends, I got myself in the situation where I have created a dynamic form (with reflection) which I now want to validate.
Example (ignore the black box, it contains old form elements which are now irrelevant and i didn't want to confuse you guys):
As you may have guessed already, it is an application for creating a mysql database.
Which is where I get to my problem(s). I want to disable checkboxes if others are checked.
For example: If I check "PrimaryKey" I want to disable the checkbox "Null".
Changing from unsigned to signed changes the numericupdown minimum and maximum etc.
But with reflection and all, I find it difficult to know exactly which checkbox to disable.
I was hoping you guys would have some suggestions.
I have been thinking about this for a while and a few thoughts have come to mind. Maybe these are better solutions than the current one.
Thought 1: I create UserControls for every datatype. Pro's: no problems with reflection and easy identifying of every control in the UserControl for validation. Con's: Copy-Pasting, Lots of UserControls, with a lot of the same controls.
Thought 2: Doing something with the description tags for every property of the classes. Creating rules in the description that allow me to link the checkboxes together. Here I'll only have to copy the rules to every class property and then it should be ok.
I had been thinking of other solutions but I failed to remember them.
I hope you guys can give me a few good pointers/suggestions.
[Edit]
Maybe my code can explain a bit more.
My code:
PropertyInfo[] properties = DataTypes.DataTypes.GetTypeFromString(modelElement.DataType.ToString()).GetType().GetProperties();
foreach (PropertyInfo prop in properties)
{
if (prop.Name != "Label" && prop.Name != "Project" && prop.Name != "Panel")
{
var value = prop.GetValue(modelElement.DataType, null);
if (value != null)
{
tableLayoutPanel1.Controls.Add(new Label { Text = prop.Name, Anchor = AnchorStyles.Left, AutoSize = true });
switch (value.GetType().ToString())
{
case "System.Int32":
NumericUpDown numericUpDown = new NumericUpDown();
numericUpDown.Text = value.ToString();
numericUpDown.Dock = DockStyle.None;
tableLayoutPanel1.Controls.Add(numericUpDown);
break;
case "System.Boolean":
CheckBox checkBox = new CheckBox();
checkBox.Dock = DockStyle.None;
// checkbox will become huge if not for these changes
checkBox.AutoSize = false;
checkBox.Size = new Size(16, 16);
if (value.Equals(true))
{
checkBox.CheckState = CheckState.Checked;
}
tableLayoutPanel1.Controls.Add(checkBox);
break;
default:
MessageBox.Show(#"The following type has not been implemented yet: " + value.GetType());
break;
}
}
}
}
Here is a mockup from my comments:
// The ViewModel is responsible for handling the actual visual layout of the form.
public class ViewModel {
// Fire this when your ViewModel changes
public event EventHandler WindowUpdated;
public Boolean IsIsNullCheckBoxVisible { get; private set; }
// This method would contain the actual logic for handling window changes.
public void CalculateFormLayout() {
Boolean someLogic = true;
// If the logic is true, set the isNullCheckbox to true
if (someLogic) {
IsIsNullCheckBoxVisible = true;
}
// Inform the UI to update
UpdateVisual();
}
// This fires the 'WindowUpdated' event.
public void UpdateVisual() {
if (WindowUpdated != null) {
WindowUpdated(this, new EventArgs());
}
}
}
public class TheUI : Form {
// Attach to the viewModel;
ViewModel myViewModel = new ViewModel();
CheckBox isNullCheckBox = new CheckBox();
public TheUI() {
this.myViewModel.WindowUpdated += myViewModel_WindowUpdated;
}
void myViewModel_WindowUpdated(object sender, EventArgs e) {
// Update the view here.
// Notie that all we do in the UI is to update the visual based on the
// results from the ViewModel;
this.isNullCheckBox.Visible = myViewModel.IsIsNullCheckBoxVisible;
}
}
The basic idea here is that you ensure that the UI does as little as possible. It's role should just be to update. Update what? That's for the ViewModel class to decide. We perform all of the updating logic in the ViewModel class, and then when the updating computations are done, we call the UpdateVisual() event, which tells the UI that it needs to represent itself. When the WindowUpdated Event occurs, the UI just responds by displaying the configuration set up by the ViewModel.
This may seem like a lot of work to set up initially, but once in place it will save you tons and tons of time down the road. Let me know if you have any questions.
Try relating the event of one checkbox to disable the other; something like this:
private void primaryKeyBox_AfterCheck(object sender, EventArgs e)
{
nullBox.Enabled = false;
}
This is a very simple example and would have to be changed a bit, but for what I think you're asking it should work. You would also have to add to an event for the boxes being unchecked. You would also need logic to only get data from certain checkboxes based on the ones that are and are not checked.
For all the other things, such as changing the numbers based on the dropdown, change them based on events as well.
For WinForms I would use data binding.
Create an object and implement INotifyPropertyChanged and work with that object.
Then, If you have an object instance aObj:
To bind the last name property to a textbox on the form do this:
Private WithEvents txtLastNameBinding As Binding
txtLastNameBinding = New Binding("Text", aObj, "LastName", True, DataSourceUpdateMode.OnValidation, "")
txtLastName.DataBindings.Add(txtLastNameBinding)
Take a look here for more info.
INotifyPropertyChanged

How to trigger validation with a button click

Upon instantiating my program, I have a textbox that already has a red border around it indicating that validation has already been triggered. I would only like the validation triggered once the user has inputted something in the textbox. This works in my other textboxes, I'm just wondering if this only happens because the binding mode is OneWayToSource, and if so, how would I be able to change this behavior to my other textboxes?
I presume you are using WPF? If so your object needs to implement IDataErrorInfo. You can then tell the textbox when it should show the error. eg:
string System.ComponentModel.IDataErrorInfo.this[string columnName]
{
get
{
switch (columnName.ToLower())
{
case "code":
if (string.IsNullOrWhiteSpace(this.Code)) return "Required field";
break;
case "name":
if (string.IsNullOrWhiteSpace(this.Name)) return "Required field";
break;
}
return null;
}
}

Listview Multiple Selection

Is there any way to force a listview control to treat all clicks as though they were done through the Control key?
I need to replicate the functionality of using the control key (selecting an item sets and unsets its selection status) in order to allow the user to easily select multiple items at the same time.
Thank you in advance.
It's not the standard behaviour of the ListView control, even when MultiSelect is set to true.
If you wanted to create your own custom control you would need to do the following:
Derive a control from ListView
add a handler to the "Selected" event.
In the "OnSelected", maintain your own list of selected items.
If the newly selected item is not in your list, add it. If it is, remove it.
In code, select all of the items in your list.
Should be simple enough to implement and feel like multi-select without using the control key!
Here is a complete solution that is a modification of the solution provided by Matthew M. above.
It offers an improvement as well as a bit of added functionality.
Improvement:
left clicking the control gives focus to the control.
right mouse click behaviour is consistent (single selection)
Added functionality:
the control has a property (MultiSelectionLimit) that allows you to put a limit on how many items can be selected at once.
After my first posting I realised a minor problem with the code. Clearing multiple selections would lead to the ItemSelectionChanged event being invoked multiple times.
I could find no way to avoid this with the current inheritance, so instead I adopted a solution where the bool property SelectionsBeingCleared will be true while until all selected items have been deselected.
This way a simple call to that property will make it possible to avoid updating effects until all of the multiple selections have been cleared.
public class ListViewMultiSelect : ListView
{
public const int WM_LBUTTONDOWN = 0x0201;
public const int WM_RBUTTONDOWN = 0x0204;
private bool _selectionsBeingCleared;
/// <summary>
/// Returns a boolean indicating if multiple items are being deselected.
/// </summary>
/// <remarks> This value can be used to avoid updating through events before all deselections have been carried out.</remarks>
public bool SelectionsBeingCleared
{
get
{
return this._selectionsBeingCleared;
}
private set
{
this._selectionsBeingCleared = value;
}
}
private int _multiSelectionLimit;
/// <summary>
/// The limit to how many items that can be selected simultaneously. Set value to zero for unlimited selections.
/// </summary>
public int MultiSelectionLimit
{
get
{
return this._multiSelectionLimit;
}
set
{
this._multiSelectionLimit = Math.Max(value, 0);
}
}
public ListViewMultiSelect()
{
this.ItemSelectionChanged += this.multiSelectionListView_ItemSelectionChanged;
}
public ListViewMultiSelect(int selectionsLimit)
: this()
{
this.MultiSelectionLimit = selectionsLimit;
}
private void multiSelectionListView_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
if (e.IsSelected)
{
if (this.MultiSelectionLimit > 0 && this.SelectedItems.Count > this.MultiSelectionLimit)
{
this._selectionsBeingCleared = true;
List<ListViewItem> itemsToDeselect = this.SelectedItems.Cast<ListViewItem>().Except(new ListViewItem[] { e.Item }).ToList();
foreach (ListViewItem item in itemsToDeselect.Skip(1)) {
item.Selected = false;
}
this._selectionsBeingCleared = false;
itemsToDeselect[0].Selected = false;
}
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_LBUTTONDOWN:
if (this.SelectedItems.Count == 0 || !this.MultiSelect) { break; }
if (this.MultiSelectionLimit > 0 && this.SelectedItems.Count > this.MultiSelectionLimit) { this.ClearSelections(); }
int x = (m.LParam.ToInt32() & 0xffff);
int y = (m.LParam.ToInt32() >> 16) & 0xffff;
ListViewHitTestInfo hitTest = this.HitTest(x, y);
if (hitTest != null && hitTest.Item != null) { hitTest.Item.Selected = !hitTest.Item.Selected; }
this.Focus();
return;
case WM_RBUTTONDOWN:
if (this.SelectedItems.Count > 0) { this.ClearSelections(); }
break;
}
base.WndProc(ref m);
}
private void ClearSelections()
{
this._selectionsBeingCleared = true;
SelectedListViewItemCollection itemsToDeselect = this.SelectedItems;
foreach (ListViewItem item in itemsToDeselect.Cast<ListViewItem>().Skip(1)) {
item.Selected = false;
}
this._selectionsBeingCleared = false;
this.SelectedItems.Clear();
}
}
You might want to also consider using Checkboxes on the list view. It's an obvious way to communicate the multi-select concept to your average user who may not know about Ctrl+Click.
From the MSDN page:
The CheckBoxes property offers a way to select multiple items in the ListView control without using the CTRL key. Depending on your application, using check boxes to select items rather than the standard multiple selection method may be easier for the user. Even if the MultiSelect property of the ListView control is set to false, you can still display checkboxes and provide multiple selection capabilities to the user. This feature can be useful if you do not want multiple items to be selected yet still want to allow the user to choose multiple items from the list to perform an operation within your application.
Here is the complete solution that I used to solve this problem using WndProc. Basically, it does a hit test when the mouse is clicked.. then if MutliSelect is on, it will automatically toggle the item on/off [.Selected] and not worry about maintaining any other lists or messing with the ListView functionality.
I haven't tested this in all scenarios, ... it worked for me. YMMV.
public class MultiSelectNoCTRLKeyListView : ListView {
public MultiSelectNoCTRLKeyListView() {
}
public const int WM_LBUTTONDOWN = 0x0201;
protected override void WndProc(ref Message m) {
switch (m.Msg) {
case WM_LBUTTONDOWN:
if (!this.MultiSelect)
break;
int x = (m.LParam.ToInt32() & 0xffff);
int y = (m.LParam.ToInt32() >> 16) & 0xffff;
var hitTest = this.HitTest(x, y);
if (hitTest != null && hitTest.Item != null)
hitTest.Item.Selected = !hitTest.Item.Selected;
return;
}
base.WndProc(ref m);
}
}
Drill down through ListviewItemCollection and you can set the Selected property for individual items to true. This will, I believe, emulate the "multi-select" feature that you are trying to reproduce. (Also, as the above commenter mentioned, be sure to have the MultiSelect property of the lisetview set to true.)
Just in case anyone else has searched for and found this article, the accepted solution is no longer valid. (in fact I am not sure it ever was). In order to do what you want (select multiple without a modifier key) simply set the list view selection type to be multiple, rather than extended. Multiple selects one item after another when clicked, and extended requires the modifier key to be pressed first.
The Ctrl+Click behavior is as implemented by the browser, and has little to do with the actual .NET Control. The result you're trying to achieve can be acquired with a lot of additional JavaScript - the easiest way would probably be to build a JavaScript control from default that works this way, rather than trying to hack up the listview. Would this be desirable? In that case I could look into it and get back to you with a solution.

Categories