How to disable controls until a condition is met? - c#

Currently in my program in about 10 control event handlers I have this code:
if (!mapLoaded)
return;
When I load a map through the open file dialog I set mapLoaded to true. Another way to do this would be to just disable all the controls for startup and after loading a map to enable all the controls. Unfortunately there are 30+ controls and this is just 30 lines of..
a.Enabled = true;
b.Enabled = true;
c.Enabled = true;
I can't really do a foreach loop through this.Controls either because some of the controls are menustrip items, toolstrip items, panel items, scrollbars, splitters, et cetera and that loop doesn't cover that.
Ideally there would be a way to set every control's enabled property to true in a single and simple loop but I'm not sure of how to do that. Any ideas SO?

Use data binding:
Change mapLoaded into a property that notifies observers when its value has changed...
public bool MapLoaded
{
get
{
return mapLoaded;
}
set
{
if (value != mapLoaded)
{
mapLoaded = value;
MapLoadedChanged(this, EventArgs.Empty);
}
}
}
private bool mapLoaded;
public event EventHandler MapLoadedChanged = delegate {};
// ^ or implement INotifyPropertyChanged instead
Data-bind your controls' Enabled property to MapLoaded. You can set up the data bindings either using the Windows Forms designer, or using code, e.g. right after InitializeComponent();:
a.DataBindings.Add("Enabled", this, "MapLoaded");
b.DataBindings.Add("Enabled", this, "MapLoaded");
c.DataBindings.Add("Enabled", this, "MapLoaded");

How about changing your opening strategy, have a new form that let's your user load a map, and the simply not load your main form until one has been loaded?

Related

WinrtXamlToolkit TreeView expand with single instead of double click

I am using the TreeView from the WinrtXamlToolkit. The default behavior of this control is to expand the nested items on double click of the header. The code responsible for this is here (TreeViewItem.cs line 1205).
private void OnHeaderMouseLeftButtonDown(object sender, PointerRoutedEventArgs e)
{
if (Interaction.AllowMouseLeftButtonDown(e))
{
// If the event hasn't already been handled and this item is
// focusable, then focus (and possibly expand if it was double
// clicked)
if (!e.Handled && IsEnabled)
{
if (Focus(FocusState.Programmatic))
{
e.Handled = true;
}
// Expand the item when double clicked
if (Interaction.ClickCount % 2 == 0)
{
bool opened = !IsExpanded;
UserInitiatedExpansion |= opened;
IsExpanded = opened;
e.Handled = true;
}
}
Interaction.OnMouseLeftButtonDownBase();
OnPointerPressed(e);
}
}
Is there a way to change this behavior to expand the items on single click or tap without actually copying the control and all it's related classes to my project?
It seems like an overkill to do this just to change a few lines of code.
I tried to do drag'n'drop stuff with that TreeView and was in a similar situation. My first move was to actually copy all the TreeView and its related classes and man there are a lot. There's a lot of internal stuff happening and I pretty much gave up interfering with it after a bunch of other stuff stopped working.
So my solution was to just have a specific control inside the ItemTemplate that handled dragging for me. For you this would be a Button whose Click you handle. In the eventhandler you will navigate up the visual tree to your TreeViewItem and change the IsExpanded.

How To Set TabPage on start?

I have a TabControl with three TabPages. The initial start of the app opens always the first TabPage on the left side. For me it's necessary to set the starting TabPage (for example the second one).
Of course, I know about possibilities to change the tab on start like these:
tabControl.SelectedTab = tabPage;
tabControl.SelectTab(tabPage);
...
But this code would also activate additional events to fire like TabControl.Selecting, TabControl.Deselecting, TabControl.SelectedIndexChanged etc. — I would really like to prevent this in advance.
What I am looking for is some kind of property in the TabControl like "StartingTabPageIndex" - setting it to 1 would open the second TabPage on start without invoking any unnecessary events.
Another option. Go into the Form Designer, change the SelectedIndex property from 0 to 1:
//
// tabControl1
//
this.tabControl1.Controls.Add(this.tabPage1);
this.tabControl1.Controls.Add(this.tabPage2);
this.tabControl1.Controls.Add(this.tabPage3);
this.tabControl1.Location = new System.Drawing.Point(223, 21);
this.tabControl1.Name = "tabControl1";
this.tabControl1.SelectedIndex = 1; // <-- This Line
this.tabControl1.Size = new System.Drawing.Size(300, 143);
this.tabControl1.TabIndex = 3;
The event handlers aren't connected yet, and making any modifications to the TabControl in the designer doesn't seem to affect that property. It seems safe to change it this way.
You should remove the binding with the event handlers from the designer and add them after you have set the initial tabpage
After removing them in the designer (this doesn't delete the event handler code) rebind the event handler in the form load event after setting the required tabpage
tabControl.SelectedTab = tabPage;
tabControl.Selected += tabControl_Selected;
.... and so on for the other events to handle....
Update
I just made a simple test, and SelectedTab does not work because it expects the handle to be created on set.
However this seems to work:
public class MyTabControl : TabControl
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Browsable(true)]
public new int SelectedIndex
{
get { return base.SelectedIndex; }
set { base.SelectedIndex = value; }
}
}
You'll now be able to see SelectedIndex in the designer and can set it. It won't change the visible tab in the designer, but it will store the "initial tab index" (zero-based).
It does change SelectedIndex, but it does not call the events since events are assigned last in the designer's serialization, so they are never assigned before the change.
Old
One option would be having SelectedTab serialized. You'd only need to derive your own custom TabControl from TabControl and have something like this:
public class MyTabControl : TabControl
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public new TabPage SelectedTab {
get { return base.SelectedTab; }
set { base.SelectedTab = value; }
}
}
That way you'll get your designer selected SelectedTab as initial.
I haven't tested this, but theory says it should work :-)

WPF C# How to avoid event fire whle importing?

I have a WPF app with a Window and different UserControls are shown in it one by one no button clicks.
I import data from a file and all data is stored in a common object "ImportExportData". All UserControls are bind to respective Property (as custom objects like Data1, Data2...) of ImportExportData class.
In my USerControl I have combobox's for NumberZones proeprty those SelectionChanged event is handled respectively. In the SelectionChanged event of this combobox, based on the number selected that many rows are added to an ObservableCollection of Data2 property.
While importing data and setting the imported object (Data2) as the DataContext of USerControl2, it sets the NumberZones property value to the respective combobox and SelectionChanged event is fired as it should. At this time, the object already contains reqd rows in ObservableCollection and this event should not add it.
PArent window has a flag "importedData" that tells me that the object is imported. But I can't make that false once UserContrl2 is loaded, as their are their UC that will follow UC2. In UC2 I can create another flag "importing" and make it false once all UI is loaded. Thru which UC event can I know that UI is loaded and thus make "importing" as false ??
I am wondering how do I avoid from firing the SelectionChanged event when the imported object is populating the UI components. Which event of the UserControl will help me in this case maybe to keep a flag in USerControl2.
Any idea, suggestions please.
It is very hard to understand all of your question, so bear with me... I'll address each point that I understand.
Thru which UC event can I know that UI is loaded and thus make "importing" as false ??
Take a look at the FrameworkElement.Loaded Event page at MSDN.
I am wondering how do I avoid from firing the SelectionChanged event when the imported object is populating the UI components.
There are two way of achieving this goal... The first way does not stop the event from firing, but instead ignores it when data is being imported. basically involves temporarily unsubscribing from the SelectionChanged event and then re-subscribing to it. If I understand you correctly, you have a bool property in your parent Window and SelectionChanged handlers in your UserControls... first, you can add a bool property to each of your UserControls:
public bool CanChangeSelection { get; set; }
Now, in your parent Window (assuming that you have references to your controls) you can update your property:
private bool isImporting = false;
public bool IsImporting
{
get { return isImporting; }
set
{
isImporting = value;
UserControl1.CanChangeSelection = isImporting;
UserControl2.CanChangeSelection = isImporting;
...
UserControlN.CanChangeSelection = isImporting;
}
}
Then finally, in your control SelectionChanged handlers:
private void SelectionChangedHandler(object sender, RoutedEventArgs e)
{
if (CanChangeSelection)
{
// do your stuff in here
}
}
The second way basically involves temporarily unsubscribing from the SelectionChanged event and then re-subscribing to it. For this option, we need to change the definition of our new bool property in each of your UserControls:
private bool canChangeSelection = false;
public bool CanChangeSelection
{
get { return canChangeSelection; }
set
{
canChangeSelection = value;
if (!canChangeSelection)
{
if (SelectionChangedHandler != null) ComboBox1.SelectionChanged -=
SelectionChangedHandler;
}
else if (SelectionChangedHandler == null) ComboBox1.SelectionChanged +=
SelectionChangedHandler;
}
}
I personally prefer the first method as it is more straightforward.

How to force validation errors update on View from ViewModel using IDataErrorInfo?

I have a MVVM-based Window with many controls, and my Model implements IDataErrorInfo.
There is also a SaveCommand button, which performs validation by analysing Model.Error property.
The view displays the default red border around controls with errors only when I change the value of a particular control, or when I notify about the change of that property using PropertyChanged.
How can I force View to display all Validation errors even when I didn't touch the controls?
All my validation bindings include ValidatesOnDataErrors=True, NotifyOnValidationError=True.
I know one solution is to have an aggregate box with all the errors, but I would prefer to display errors on per-control basis.
I don't want to trigger Model.NotifyPropertyChanged for each bound property from ViewModel.
I use WPF 4.0, not Silverlight, so INotifyDataErrorInfo won't work.
You mention that you don't want to raise property changed for the properties you bind to, but that's really the simplest way to accomplish this. Calling PropertyChanged with no parameter will raise for all properties in your viewmodel.
Alternatively you can update the bindings (and force revalidation) on any control like this:
myControl.GetBindingExpression(ControlType.ControlProperty).UpdateSource();
The best solution I've found so far that works is to change DataContext to null and back to the instance of ViewModel.
This triggers the update for controls on the view that has DataContext bound to InnerViewModel:
public void ForceUpdateErrors() {
var tmpInnerVM = _mainViewModel.InnerViewModel;
_mainViewModel.InnerViewModel = null;
_mainViewModel.InnerViewModel = tmpInnerVM;
}
It's recommended to check if no data is lost after this trick. I had a case that this code triggered source update for ComboBox.SelectedItem with null but I managed to solve it. It was caused by using a resource-based BindingProxy and the order of DataContext=null propagation across control hierarchy.
This 'Hack' worked for me temporarily, to force the InotifyChanged event, just assign that control back it's own content. Do this before evaluating the HasError function of bindings. For example a textbox would be:
((TextBox)child).Text = ((TextBox)child).Text;
And then a complete example(before I hear this is not true MVVM, I directly got a handle on the grid for ease of showing this code snipet)
public bool Validate()
{
bool hasErr = false;
for (int i = 0; i != VisualTreeHelper.GetChildrenCount(grd); ++i)
{
DependencyObject child = VisualTreeHelper.GetChild(grd, i);
if (child is TextBox)
{
bool pp = BindingOperations.IsDataBound(child, TextBox.TextProperty);
if (pp)
{
((TextBox)child).Text = ((TextBox)child).Text;
hasErr = BindingOperations.GetBindingExpression(child, TextBox.TextProperty).HasError;
System.Collections.ObjectModel.ReadOnlyCollection<ValidationError> errors = BindingOperations.GetBindingExpression(child, TextBox.TextProperty).ValidationErrors;
if (hasErr)
{
main.BottomText.Foreground = Brushes.Red;
main.BottomText.Text = BindingOperations.GetBinding(child, TextBox.TextProperty).Path.Path.Replace('.', ' ') + ": " + errors[0].ErrorContent.ToString();
return false;
}
}
}
if (child is DatePicker)
{
...
}
}
return true;
}

C# grid binding not update

I have a grid that is binded to a collection. For some reason that I do not know, now when I do some action in the grid, the grid doesn't update.
Situation : When I click a button in the grid, it increase a value that is in the same line. When I click, I can debug and see the value increment but the value doesn't change in the grid. BUT when I click the button, minimize and restore the windows, the value are updated... what do I have to do to have the value updated like it was before?
UPDATE
This is NOT SOLVED but I accepted the best answer around here.
It's not solved because it works as usuall when the data is from the database but not from the cache. Objects are serialized and threw the process the event are lost. This is why I build them back and it works for what I know because I can interact with them BUT it seem that it doesn't work for the update of the grid for an unkown reason.
In order for the binding to be bidirectional, from control to datasource and from datasource to control the datasource must implement property changing notification events, in one of the 2 possible ways:
Implement the INotifyPropertyChanged interface, and raise the event when the properties change :
public string Name
{
get
{
return this._Name;
}
set
{
if (value != this._Name)
{
this._Name= value;
NotifyPropertyChanged("Name");
}
}
}
Inplement a changed event for every property that must notify the controls when it changes. The event name must be in the form PropertyNameChanged :
public event EventHandler NameChanged;
public string Name
{
get
{
return this._Name;
}
set
{
if (value != this._Name)
{
this._Name= value;
if (NameChanged != null) NameChanged(this, EventArgs.Empty);
}
}
}
*as a note your property values are the correct ones after window maximize, because the control rereads the values from the datasource.
It sounds like you need to call DataBind in your update code.
I am using the BindingSource object between my Collection and my Grid. Usually I do not have to call anything.

Categories

Resources