Binding to checked propert messes up ui, binding to text is fine - c#

UPDATE:
I think i have found the problem.
All my forms events that affect the binding source have this in the end:
BndSource.ResetBindings(false);
If i comment this line in my CheckedChanged event handler, the issue stops. But why?
I have a very strange bug.
I have a class property:
public SqlByte AutomaticFlag { get; set; }
I wanted to use checkbox to facilitate for showing this so in initial inding i do this:
dtaAutomaticFlag.DataBindings.Add("Checked", BndSource, "AutomaticFlag", true);
dtaAutomaticFlag.DataBindings[0].Format += (s, e) =>
{
if ((SqlByte)e.Value == 1)
{
e.Value = true;
}
else
{
e.Value = false;
}
};
the problem is that during iteration through all records of the binding source my ui is half updated, meaning its not complete. See picture:
VERY strangely when i change the above binding property from checked to text like this:
dtaAutomaticFlag.DataBindings.Add("Text", BndSource, "AutomaticFlag", true);
the ui is ok!!
Picture:

I'm not sure if this applies to this particular situation. But rather than adding the binding as you did:
dtaAutomaticFlag.DataBindings.Add("Text", BndSource, "AutomaticFlag", true);
Does creating a "new" binding instance help at all?
dtaAutomaticFlag.DataBindings.Add(new Binding("Text", BndSource, "AutomaticFlag", true));

Related

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

Programmatically select a specific TreeViewItem into an unbound TreeView

In the code behind of my Silverlight application, I have the need to re-populate the TreeView and then make a specific TreeViewItem selected.
The code itself is pretty simple, here it is (i'll trim and pseudo-code-ify it to make it as short as possible)
private void Button_Click()
{
Guid idToSelect = TellMeWhatToSelect();
List<myObject> myDataList = myObjectRepository.RetrieveData().ToList();
myTreeView.Items.Clear();
foreach(myObject o in myDataList)
{
myTreeView.Items.Add(new TreeViewItem() { Content = o.DataField, Tag = o.Id });
}
myTreeView.Items.First(o => ((Guid)(o as TreeViewItem).Tag).Equals(idToSelect)).IsSelected = true;
}
That's basically it: i'm reading some data into myDataList, then i cycle through it and create as many TreeViewItems as needed in order to display the data.
Problem is, myTreeView.SelectedItem is null at the end of this, and SelectionChanged event isn't triggered. I would think that, since Items collection has been cleared and re-filled, switching IsSelected on one of the items would act like clicking, but it seems it doesn't).
Oddly enough (for me at least), issuing myTreeView.Items.First().IsSelected = true; by itself (that is, calling a method with that single line of code inside) works as expected: SelectedItem is there and all events are fired appropriateyl.
What's wrong with my code and/or what am I missing ? Looks like cleaning items up kind of breaks something.
I'm fairly sure others have had similar issues, but a bunch of searches I tried didn't help (most of the info & questions I came up with are WPF-related).
Thanks for your time, I'll provide more info if needed. Also, sorry for the wall of text.
UPDATE
Modifying code like this, now the method works as expected.
private void Button_Click()
{
Guid idToSelect = TellMeWhatToSelect();
List<myObject> myDataList = myObjectRepository.RetrieveData().ToList();
myTreeView.Items.Clear();
foreach(myObject o in myDataList)
{
myTreeView.Items.Add(new TreeViewItem() { Content = o.DataField, Tag = o.Id });
}
Dispatcher.BeginInvoke(()=>
{
myTreeView.Items.First(o => ((Guid)(o as TreeViewItem).Tag).Equals(idToSelect)).IsSelected = true;
});
}
Set property IsSelected inside of Dispatcher.BeginInvoke.
I had the same problem a while ago, I've solved by calling the UpdateLayout method from the treeview before setting the TreeViewItem as selected.Like this:
myTreeView.UpdateLayout();
myTreeView.Items.First(o => ((Guid)(o as TreeViewItem).Tag).Equals(idToSelect)).IsSelected = true;

When to render custom WPF TextBlock?

When should I be building Inlines in a TextBlock? I have a TextBlock-derived class that, when given text in a certain field, call it MyText, converts the text into a set of inlines when MyText has changed.
Whenever MyText changes, I clear the Inlines and build them, colorizing each word as needed. For this example, consider:
private void MyTextBlock_MyTextChanged(object sender, EventArgs e)
{
Inlines.Clear();
if (!string.IsNullOrEmpty(this.MyText))
{
var run = new Run();
run.Foreground = Brushes.DarkRed;
run.Text = this.MyText;
Inlines.Add(run);
}
}
This has worked very well. However, recently we placed the Control into a DataGrid, and some strange things have started happening. Apparently the DataGrid swaps out the context and for the most part this works. However, when we add or delete data from the DataGrid ItemsSource, something goes awry, and TextChanged doesn't seem like it is called (or at least not called at the same time). MyText can be one value, and the Inlines either blank or a different value.
I think that the place to build the Inlines is NOT during MyTextChanged, but maybe when the rendering of the Control starts. I've also tried when the DataContextChanged, but this does not help.
In my constructor, I have
this.myTextDescriptor = DependencyPropertyDescriptor.FromProperty(
MyTextProperty, typeof(MyTextBlock));
if (this.myTextDescriptor != null)
{
this.myTextDescriptor.AddValueChanged(this, this.MyTextBlock_MyTextChanged);
}
corresponding to a dependency property I have in the class
public string MyText
{
get { return (string)GetValue(MyTextProperty); }
set { SetValue(MyTextProperty, value); }
}
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register("MyText", typeof(string), typeof(MyTextBlock));
private readonly DependencyPropertyDescriptor myTextDescriptor;
Update: If it is any kind of clue, the problem DataGrid cells seem to be the ones that are off-screen when the addition or removal happens. I also tried OnApplyTemplate, but that didn't help.
Update2: Perhaps a better solution might be to create bindable inlines?
DataGrids virtualize their content, so if a row is not visible it will not be loaded. That being the case, have you tried also rebuilding the inlines when the Loaded event fires?

Determine in code whether to allow a dragdrop drop using the Silverlight Toolkit

I'm using the SilverLight Toolkit to implement some drag/drop functionality in a Silverlight 4 web application. My drag source is a listbox, and I've got eighteen potential drop targets. I need to allow/disallow dropping based on a string value on the dragged object.
I have no problem doing the comparison and determining whether or not the item is allowed to be dropped on the target, however, I'm having trouble figuring out what the best event is to handle, and how to make it not accept the drop.
I've looked at the DragEnter event, and that looks like the best place to handle this, but I'm not quite sure what I need to do to make it not drop. Here's a snippet of some of the code that I've tried, but it doesn't prevent the drop:
lbDragDrop.DragEnter += (src, e) =>
{
VaultSocketViewModel vm = this.DataContext as VaultSocketViewModel;
ListBoxDragDropTarget target = src as ListBoxDragDropTarget;
ObservableCollection<ItemModel> listBoxBinding = vm.Slots[target.Name];
object data = e.Data.GetData(e.Data.GetFormats()[0]);
ItemDragEventArgs eventArgs = data as ItemDragEventArgs;
SelectionCollection coll = eventArgs.Data as SelectionCollection;
ItemModel newItem = coll.Select(t => t.Item).OfType<ItemModel>().FirstOrDefault();
if (!target.Name.StartsWith(newItem.ItemSlot)) // don't allow drop
{
e.Effects = Microsoft.Windows.DragDropEffects.None;
e.Handled = true;
}
else
{
}
};
just change the Effects to None (like you're doing) should be enough - a good example is some of the internal code in the treeview drag drop target in the toolkit itself (the SetEffects method)
http://silverlight.codeplex.com/SourceControl/changeset/view/57505#779753
Well, I was close. As #James Manning said in his answer, "just change the Effects to None ... should be enough."
So, true, as long as you do it in the right place. I had put my code to handle this in the DragEnter event handler, when it should have been done in the DragOver event handler. Changing the effects in DragEnter are like Rainier Wolfcastle's Radioactive Man goggles-- they do nothing.
So, the code that works is as follows:
lbDragDrop.DragOver += (src, e) =>
{
VaultSocketViewModel vm = this.DataContext as VaultSocketViewModel;
ListBoxDragDropTarget target = src as ListBoxDragDropTarget;
ObservableCollection<ItemModel> listBoxBinding = vm.Slots[target.Name];
object data = e.Data.GetData(e.Data.GetFormats()[0]);
ItemDragEventArgs eventArgs = data as ItemDragEventArgs;
SelectionCollection coll = eventArgs.Data as SelectionCollection;
ItemModel newItem = coll.Select(t => t.Item).OfType<ItemModel>().FirstOrDefault();
if (!target.Name.StartsWith(newItem.ItemSlot)) // don't allow drop
{
e.Effects = Microsoft.Windows.DragDropEffects.None;
e.Handled = true;
}
else
{
}
};

Silverlight: Two-Way binding on ComboBox doesn't hit BindingValidationError handler with null

I've scoured the internet and have yet to find a solution. Help me Stack-Overflow-Kenobi, you're my only hope!
So I've got this Silverlight application, right? And I have a combobox that's bound to a non-nullable database field so that it is populated on initialization with all possible values. And it's working fine in that regard.
However, when I SubmitChanges without having selected an item, no validation error is thrown, so my BindingValidationError handler is never activated. Now I would expect (and kinda need) an error to be thrown when pushing null into a non-nullable database column. That way the user knows to select an item.
When the value is not null, it is pushed to the database just fine. Basically, the binding works fine: I just need to know why the BindingValidationError handler is not hit. ToggleError needs to be run if no item is selected.
foo()
{
Binding databinding = new Binding(this.Id);
databinding.Source = bindingObject;
databinding.BindsDirectlyToSource = true;
databinding.Mode = BindingMode.TwoWay;
databinding.ValidatesOnDataErrors = true;
databinding.ValidatesOnExceptions = true;
databinding.ValidatesOnNotifyDataErrors = true;
databinding.NotifyOnValidationError = true;
databinding.UpdateSourceTrigger = UpdateSourceTrigger.Default;
CmbBox.DisplayMemberPath = _DisplayMemberPath;
CmbBox.SelectedValuePath = _SelectedValuePath;
CmbBox.SetBinding(ComboBox.SelectedItemProperty, databinding);
CmbBox.BindingValidationError += (sender, e) => ToggleError(e.Action == ValidationErrorEventAction.Added ? true : false , e.Error.ErrorContent.ToString());
}
private void ToggleError(bool enableError, string errorMessage)
{
hasError = enableError;
if (hasError)
{
CmbBox.Foreground = new SolidColorBrush(Utilities.DarkRed);
Error.Visibility = Visibility.Visible;
this.errorMessage = errorMessage;
}
else
{
CmbBox.Foreground = new SolidColorBrush(Utilities.DarkGreen);
Error.Visibility = Visibility.Collapsed;
errorMessage = null;
}
}
Thank in advance : )
Cameron
The BindingValidationError event fires when a TwoWay Binding is updated and the setter throws an exception. If you never select a value for the ComboBox then the Binding is never updated and it will never throw an error. You need to do validation yourself before you call SubmitChanges.
If you are using Silverlight 4 you may want to look into using INotifyDataErrorInfo to do validation in your code and then update the UI to show the validation error.

Categories