Here, I have a bit confusion about UI language. If language is changed then what happens? The whole folder gets changed or Culture gets loaded? I cannot get what is actually happening.
Properties.Strings.MainWindow_Language_Selection_English_Label="English"
Properties.Strings.MainWindow_Language_Selection_Gujarati_Label="ગુજરાતી"
Please explain what is happening.
private void LanguageSelection_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBoxItem item = LanguageSelection.SelectedItem as ComboBoxItem;
if (item.Content.ToString() == Properties.Strings.MainWindow_Language_Selection_English_Label)
{
CultureManager.UICulture = new System.Globalization.CultureInfo("en");
}
else if (item.Content.ToString() == Properties.Strings.MainWindow_Language_Selection_Gujarati_Label)
{
CultureManager.UICulture = new System.Globalization.CultureInfo("gu");
}
Settings.Default["UILanguage"] = CultureManager.UICulture.Name;
Settings.Default.Save();
}
In general, setting the culture on application thread will be effective on the next form that is displayed, so to make this work you probably need a login/language selection window where you set the main thread's culture and then show application's main window.
There were a few attempts around this to make language selection take effect immadiately (easier in WPF) but this is how it works out of the box.
In WPF, however, if you are directly binding UI elements to resources you can make the UI update by raising a property change event on your resource property. The easiest way to achieve this (other than creating a new code generator for the .resx file) would be to wrap your resources in a model class like this:
public class StringRes : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate {};
public string Login
{
get { return Properties.Strings.Login; }
}
public string Password
{
get { return Properties.Strings.Password; }
}
public void NotifyLanguageChanged()
{
PropertyChanged(this, new PropertyChangedEventArgs("Login"));
PropertyChanged(this, new PropertyChangedEventArgs("Password"));
}
}
public class MainWindow
{
private StringRes _resources;
private void LanguageSelection_SelectionChanged()
{
System.Threading.Thread.CurrentThread.CurrentUICulture = GetCurrentCulture();
_resources.NotifyLanguageChanged();
}
}
If you have bound your UI elements to the instance of the StringRes class, they will be updated when you raise the notification change event in your model.
Related
so I have a model which contains 2 variables, a List and a DateTime. In my UserControl I have a DependencyProperty and I also defined a PropertyChangedCallback.
public static readonly DependencyProperty MyProperty = DependencyProperty.Register("My", typeof(List<MyContainer>), typeof(UC), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnMyProperty)));
public List<MyContainer> My
{
get
{
return GetValue(MyProperty) as List<MyContainer>;
}
set
{
SetValue(MyProperty, value);
}
}
private static void OnMyProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UC control = d as UC;
//do stuff
}
On my form there is a button, which do the changes on the other model variable (on the DateTime).
private void Date_Click(object sender, RoutedEventArgs e)
{
MyModel model = DataContext as MyModel;
if (model != null)
{
model.Date = model.Date.AddDays(1);
}
}
And finally here is my model.
public class MyModel : INotifyPropertyChanged
{
private List<MyContainer> _My;
private DateTime _Date;
public MyModel()
{
_Date = DateTime.Now.Date;
_My = new List<MyContainer>();
}
public List<MyContainer> My
{
get
{
return _My;
}
set
{
_My = value;
OnPropertyChanged("My");
}
}
public DateTime Date
{
get
{
return _Date;
}
set
{
_Date = value;
OnPropertyChanged("Date");
OnPropertyChanged("My");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
XAML declaration is the following.
<local:UC My="{Binding My}" />
So my problem is the after I hit the run, it fires the OnMyProperty once, after that if I hit the button, it changes the DateTime property well, but the OnMyProperty callback doesn't firing again. However I noticed that if I modify my model like this
public DateTime Date
{
get
{
return _Date;
}
set
{
_Date = value;
_My = new List<MyContainer>(_My); //added
OnPropertyChanged("Date");
OnPropertyChanged("My");
}
}
now it fires it every time when I hit the button. How can I trigger the second behaviour without that modification?
After setting the value of a DependencyProperty it first checks if the new value is different to the old one. Only in this case the PropertyChangedCallback method you registered with that DependencyProperty is called. So the name PropertyChanged makes sense.
In your (not modified) case you not even try to change My (only Date). So there is no reason to raise the callback function.
The answer is that you almost certainly do not need to do this. When you ask a question about how to make the framework do something it really does not want to do, always say why you think you need to do that. It's very likely that there's a much easier answer that everybody else is already using.
The only thing you have bound to the control is My. Therefore, if My hasn't changed, then the state of the control should not change. If you want the state of the control to change when Date changes, bind Date to some property of the control. The only way the control should ever get information from any viewmodel is through binding one of its dependency properties to a property of the viewmodel.
The control should not ever know or care who or what is providing values for its properties. It should be able to do its job knowing only the property values it has been given.
If the contents of My have changed -- you added an item or removed one -- of course the control has no way of knowing that, because you refused to tell it. You're just telling it there's a new list. It checks, sees it's still got the same old list, and ignores you. The My property of your viewmodel should be an ObservableCollection, because that will notify the control when you add or remove items in the collection.
The items themselves, your MyContainer class, must implement INofityPropertyChanged as well, if you want to be able to change their properties while they are displayed in the UI.
The dependency property My on your control must not be of type List<T>. It should probably be type object, just like ItemsControl.ItemsSource. Then your control template can display it in an ItemsControl which knows what to do with it. If an ObservableCollection is bound to it as I suggested above, the ItemsControl will update automatically. In OnMyProperty, your control class can check to see if it's an observable collection as well:
private static void OnMyProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UC control = d as UC;
if (e.NewValue is INotifyCollectionChanged)
{
(e.NewValue as INotifyCollectionChanged).CollectionChanged +=
(s, ecc) => {
// Do stuff with UC and ecc.NewItems, ecc.OldItems, etc.
};
}
}
I have Windows Forms application with one main form (derived from base Form). Other modal forms that could be opened there are derived from my class ManagedForm, which is also derived from Form.
Also I have a static notifier service which fires some events like this:
public static class NotifierService
{
public delegate void NotifierServiceEventHandler(object sender, NotifierServiceEventArgs e);
private static readonly object Locker = new object();
private static NotifierServiceEventHandler _notifierServiceEventHandler;
#region Events
public static event NotifierServiceEventHandler OnOk
{
add
{
lock (Locker)
{
_notifierServiceEventHandler += value;
if (
_notifierServiceEventHandler.GetInvocationList()
.Count(
_ =>
_.Method.DeclaringType != null &&
value.Method.DeclaringType != null &&
_.Method.DeclaringType == value.Method.DeclaringType) <= 1)
return;
_notifierServiceEventHandler -= value;
}
}
remove
{
lock (Locker)
{
_notifierServiceEventHandler -= value;
}
}
}
// and many more events similar to previous...
#endregion
#region Event firing methods
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
NotifierServiceEventHandler handler;
lock (Locker)
{
handler = _notifierServiceEventHandler;
}
if (handler == null) return;
handler(typeof (NotifierService),
new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
}
#endregion
}
So in some places of code these events could be fired like:
NotifierService.NotifyExclamation("Fail!");
In the main form there is StatusStrip control used for notification purposes, and due to main form has subscribtion to these events -- their messages will be shown in the status strip.
BUT!, as I've said earlier, user may open other forms, and these forms could produce others and so on... (they are derived from one class ManagedForm which will be subscribed to NotifierService as soon as it has been created).
In these forms there is another logic how to notify user -- they need to show MessageBoxes with messages. As you can see, I've added some magic in event accessors to allow only one subscriber of any type, because w/o this all opened forms will generate their own MessageBoxes. But when one child ManagedForm has produced another and the second has been closed -- no MessageBoxes will be shown.
What magic should I implement to allow subscription from only first ManagedForm? Many thanks for any ideas.
EDIT: Suggested ideas doesn't solve this issue. I've tried to change event to this:
private static readonly object Locker = new object();
private static EventHandler<NotifierServiceEventArgs> _myEvent;
public static event EventHandler<NotifierServiceEventArgs> OnOk
{
add
{
if (_myEvent == null || _myEvent.GetInvocationList().All(_ => _.Method.DeclaringType != value.Method.DeclaringType))
{
_myEvent += value;
}
}
remove
{
_myEvent -= value;
}
}
Then I've open one modal child form and create a situation in which event has been fired by NotifierService. One MessageBox has been generated and shown (that's OK). Afterwards I've opened another modal form from first and create another situation in which another event has been fired. One MessageBox has been generated and shown (that's also OK). Now I'm closing second form and making a situation needed to fire event. No MessageBoxes has been shown (but in the status strip of the main form message of event has been shown correctly, so nothing has been changed from my first implementation).
Should I change something in remove clause? I do not need that only one subscriber should be, I need that each of the subscribers should be of distinct types. Sorry If bad English.
The way you are trying to solve the problem is fundamentally wrong by design. Your service class defines an event that will be fired under some circumstances. Some clients subscribe to that event, this way requesting to be notified when it happened. This is simply the .NET way of implementing the Observer pattern, so your service (being the subject or observable), should not apply any logic neither at subscribe nor the notify part, thus defeating the whole purpose of the pattern. Hans Passant already pointed to some flaws in your design, but even his solution is not perfect because looking at the event signature, it's totally unclear that only form instance methods are supposed to be registered - one can try using static method, anonymous lambda/method, some class method etc.
So, IMO the following are some of the viable choices you have.
(A) Keep your NotificationService events, but remove any "magic" from both subscribe and notify parts (shortly, use the regular way of defining and firing an event) and put the logic needed in your subscribers:
public static class NotifierService
{
public delegate void NotifierServiceEventHandler(object sender, NotifierServiceEventArgs e);
public static event NotifierServiceEventHandler OnOk;
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
var handler = OnOk;
if (handler != null)
handler(typeof(NotifierService), new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
}
}
Assuming that only the active form is supposed to handle the notifications, the existing handlers in both your MainForm and ManagedForm would use something like this inside their method body
if (this != ActiveForm) return;
// do the processing
You can even create a base form like this
class NotifiedForm : Form
{
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
NotifierService.OnOk += OnNotifyOK;
// similar for other events
}
protected override void OnDeactivate(EventArgs e)
{
base.OnDeactivate(e);
NotifierService.OnOk -= OnNotifyOK;
// similar for other events
}
protected virtual void OnNotifyOK(object sender, NotifierServiceEventArgs e) { }
// similar for other events
}
and let your MainForm, ManagedForm (and any other is needed) inherit from that and just override the OnNotifyXXX methods and apply their logic.
To conclude, this approach would keep your service abstract and will leave the decisions to the clients of the service.
(B) If the sole purpose of your service is to act like a notification coordinator specifically for your forms, then you can remove events along with subscribe/unsubscribe parts (since Application.OpenForms and Form.ActiveForm already provide enough information needed) and handle the logic in your service. In order to do that, you'll need some sort of a base interface(s) or forms, and the easiest would be to use a similar approach to what was optional in the option (A) by creating a base form class like this
class NotifiedForm : Form
{
public virtual void OnNotifyOK(object sender, NotifierServiceEventArgs e) { }
// similar for other notifications
}
and let your MainForm, ManagedForm and other needed inherit from it. Note that there is no logic here (checking ActiveForm etc.) because now that's the responsibility of the caller. Then the service could be something like this:
public static class NotifierService
{
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
var target = Form.ActiveForm as NotifiedForm;
if (target != null)
target.OnNotifyOK(typeof(NotifierService), new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
}
// similar for other notifications
}
if the logic is to notify only the active form.
Or
public static class NotifierService
{
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
// Could also be a forward for, forach etc.
for (int i = Application.OpenForms.Count - 1; i >= 0; i--)
{
var target = Application.OpenForms[i] as NotifiedForm;
if (target != null /* && someOtherCritaria(target) */)
{
target.OnNotifyOK(typeof(NotifierService), new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
// Could also continue
break;
}
}
}
// similar for other notifications
}
if some other logic is needed (which I doubt).
Hope that helps. In any case, option (A) is more flexible and allows much more usage scenarios, but if the usage scenarios are fixed by design, then the option (B) is better because it requires less from the clients (thus being less error prone) and provides a centralized application logic in one place.
I would like you proceed as follows:
Remove the magic from event accessor method and let all the subscribers subscribe to the event. So now you will have your main form and all other forms subscribed to the event.
Now place the magic in your event invocation method. For example in your NotifyOK method, first get the invocation list of deligate, now invoke each deligate one by one using DynamicInvoke or Invoke method of each deligate in the invocation list only if you have not already invoked for the particular DeclaringType. See the algo below:
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
NotifierServiceEventHandler handler;
lock (Locker)
{
handler = _notifierServiceEventHandler;
}
if (handler == null) return;
// Get invocation list of handler as you have done in event accessor
//initialise a new List<T> to hold the declaring types
// loop through each member (delegate) of invocation list
// if the current member declaration type is not in List<t>
// Invoke or DynamicInvoke current delegate
// add the declaration type of current delegate to List<t>
}
Try this:?)
private bool _eventHasSubscribers = false;
private EventHandler<MyDelegateType> _myEvent;
public event EventHandler<MyDelegateType> MyEvent
{
add
{
if (_myEvent == null)
{
_myEvent += value;
}
}
remove
{
_myEvent -= value;
}
}
i have reduced NotifierService to this:
public static class NotifierService
{
public static event EventHandler<NotifierServiceEventArgs> OnOk = delegate { };
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
OnOk(typeof(NotifierService),
new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
}
}
and then in ManagedForm used this handler
NotifierService.OnOk += Notify;
private void Notify(object sender, NotifierServiceEventArgs e)
{
// handle event in first open ManagedForm
if (Application.OpenForms.OfType<ManagedForm>().FirstOrDefault() == this)
{
// notification logic
}
}
if forms are opened as Modal (using ShowDialog()), it is possible to use another variant (according to this question):
private void Notify(object sender, NotifierServiceEventArgs e)
{
// handle event in active (last shown) ManagedForm
if (this.CanFocus)
{
// notification logic
}
}
so the idea is that all ManagedForms receive event data and then decide should they do something or not
P.S.: unsubscribe handlers on Dispose
protected override void Dispose(bool disposing)
{
if (disposing)
{
NotifierService.OnOk -= Notify;
}
// default
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
I have made a setup similar to yours & I see the problem.
I'll give 2 working suggestion to fix the issue (you may choose as per the changes required) -
Quickest fix with minimal changes to your original code -
So this is what I understand from the problem situation - You hooked event NotifierService.OnOk to an event handler in class ManagedForm & also wrote code to unhook the event handler from event NotifierService.OnOk when the form closes.
I'm assuming that you wrote the code to unhook the event handler from event NotifierService.OnOk when the form closes
But what I'm not sure is that when do you hook event NotifierService.OnOk to its event handler in managed form. Thats critical & I guess thats the only problem in your setup.
I assume you have set it up at a place which happens only once in the lifetime of form - like constructor or Load Event handler. And thats how I could reproduce the problem.
As fix, Just move hooking the event NotifierService.OnOk to its event handler at a place which which is called everytime the form becomes active
like
something like this -
public partial class ManagedFrom : Form
{
// this is the fix. Everytime the form comes up. It tries to register itself.
//The existing magic will consider its request to register only when the other form is closed or if its the 1st of its type.
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
NotifierService.OnOk += NotifierService_OnOk;
}
No more change needed, your existing logic in the event will take care of rest.
I have written the reason as comment in code above.
A little Better way but needs more changes
I would like to relieve the event OnOk form all the additional (& magical) responsibilities, I change the event
public static event NotifierServiceEventHandler OnOk
{
add
{
lock (Locker) // I'm not removing the locks. May be the publisher works in a multithreaded business layer.
{
_notifierServiceEventHandler += value;
}
}
remove
{
lock (Locker)
{
_notifierServiceEventHandler -= value;
}
}
}
Instead the subscriber should know when to Start and when to stop the subscription.
Therefore I change ManagedFrom
public partial class ManagedFrom : Form
{
//start the subscription
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
NotifierService.OnOk += NotifierService_OnOk;
}
//stop the subscription
protected override void OnDeactivate(EventArgs e)
{
base.OnDeactivate(e);
NotifierService.OnOk -= NotifierService_OnOk;
}
In both the suggestions, my intend is to just fix the issue without introducing any new pattern. But do let me know if thats needed.
Also do let me know if it was helpful or if you think I took any wrong assumption .
To sum up:
there are multiple sources of events;
there are multiple targets;
there are different types of events which have to be processed differently.
Idea to use static manager is ok (unless you have performance issues, then splitting into multiple different message queues is the option), but cheating with subscribing/unsubscribing feels so wrong.
Make a simple event
public enum MessageType { StatusText, MessageBox }
public NotifyEventArgs: EventArgs
{
public MessageType Type { get; }
public string Message { get; }
public NotifyEventArgs(MessageType type, string message)
{
Type = type;
Message = message;
}
}
public static NotifyManager
{
public event EventHandler<NotifyMessageArgs> Notify;
public static OnEventHandler(MessageType type, string message) =>
Notify?.Invoke(null, new NotifyEventArgs(type, message));
}
Each form has to subscribe to this event when shown and unsubscribe when hidden. Not sure which events are the best here (got used to much to WPF Loaded, Unloaded, but there is no such in winforms, try to use Shown or VisibilityChanged perhaps).
Each form will receive event, but only one has to process MessageBox type (it is safe for all of them to display StatusMessage). For this you need some mechanizm to decide whenever form is the one (used to display message boxes). E.g. it can be active form:
void NotifyManager_Event(object sender, NotifyEventArgs e)
{
if(e.Type == MessageType.MessageBox && this == Form.ActiveForm)
MessageBox.Show(this, e.Message);
else
statusBar.Text = e.Message;
}
Are you sure that it is the task of the NotifierService to make sure that only one Form will show the notification?
If you would describe the tasks of a NotifierService, you would describe what it does and "whenever the NotifierService has something to notify, it will notify everyone who said that it wanted to be notified about the notifications"
This would make your notifierservice less dependant of the current application where it is used. If you want a completely different application with for instance only two Forms, where you want both Forms to react on the notifications you could not use this notifierservice.
But in my Forms application only one form may react on the notifications
That is right: it is your Forms application that has this constraint, not the notifierservice. You make a Forms aplication that may use any kind of notifierservice, but whatever notifierservice is used, only one of the Forms in my application may show the notification.
This means that you should have some rule to know whether a form should show the notifications or not
For instance:
Only the current form may show the notifications
Only the top left form may show the notifications
Only the main form may show the notifications, except when the settings form is visible
So let's assume you have something to determine which Form or Forms may react on notifications. This changes upon something happening: a form becomes active, or a form closes, a form becomes invisible, whatever.
Make a Boolean property for a ManagedForm that holds whether it should show notifications:
class ManagedForm
{
public bool ShowNotifications {get; set;}
public void OnEventNotification(object sender, ...)
{
if (this.ShowNotifications)
{
// show the notification
}
}
Now someone has to know which form should show the notification. This someone should set property ShowNotification.
For instance if only the active ManagedForm should show the notifications then the ManagedForm can decide for itsels:
public OnFormActiveChanged(object sender, ...)
{
this.ShowNotifications = this.Form.IsActive;
}
If all red Forms should show the notifications:
public OnFormBackColorChanged(object sender, ...)
{
this.ShowNotifications = this.Form.BackColor == Color.Red;
}
If you have a lot of Forms, with only a few that show notifications, then a lot events OnShowNotification will be called for nothing, but since this is just a function call it won't be a problem unless you show 1000 forms or so, and I guess in that you have more serious problems.
Summerized
Decide the criterium on which a ManagedForm should show the notifications
Decide when a different form should show the notifications
Create an event handler for when the form changes, let the event handler set property ShowNotification
When the event to show the notification occurs, check the property.
Subscriptions are useful if you actually want these events to propagate to each form, but that doesn't seem like what you want to do. Given any action, your code is needing to show only one dialog box and update the status text of the main form.
Maybe you should consider using a singleton pattern, instead. By using a static event handler, this is essentially what you are already doing.
public class MainAppForm : Form
{
static MainAppForm mainAppForm;
public MainAppForm()
{
mainAppForm = this;
}
public static void NotifyOk(Form sender, string fullMessage = "Ok.", string shortMessage = null)
{
mainAppForm.NotifyOk(sender, fullMessage, shortMessage);
}
public void NotifyOk(Form sender, string fullMessage, string shortMessage)
{
this.statusStrip.Invoke(delegate {
this.statusStrip.Text = shortMessage;
});
}
}
I have been struggling with this for a while now. I have a Master / Details layout in my application, and am faced, like many others, with the problem of the DataGrid loosing its selection when disabling it. Essencialy, after selecting an element from the list to populate a series of fields, the user presses "Edit", wich disables the DataGrid and enables all of the form's fields. Pressing the "Save" button will revert these actions after saving the data... Pretty strait forward.
I am on Windows 7 developping with VS 2010 in the .Net Framework 4.
What I have tried:
1) Based on this post, I have tried to use the DataGrid in the June 2009 version of the WPF Toolkit, but I had the same reaction.
2) Based on this WPF CodePlex bug report, I have tried to create a custom control based on the DataGrid and to override the OnIsEnabledChanged call to remove the call to "UnselectAllCells", but with no code example, I can't even get it to fire once. I have tried:
public class FormMainDataGrid : DataGrid
{
static FormMainDataGrid()
{
IsEnabledProperty.OverrideMetadata(typeof(FormMainDataGrid), new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsEnabledChanged)));
}
public FormMainDataGrid() : base() { }
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(CanUserAddRowsProperty);
d.CoerceValue(CanUserDeleteRowsProperty);
//this was added in new version !!!
/*
if (!(bool)(e.NewValue))
{
((DataGrid)d).UnselectAllCells();
}
*/
// Many commands use IsEnabled to determine if they are enabled or not
CommandManager.InvalidateRequerySuggested();
}
}
but this still unselects the currently selected row as soon as I disable the DataGrid. I have tried to interprete the last comments (in the Codeplex bug report) like this:
public class FormMainDataGrid : DataGrid
{
static FormMainDataGrid()
{
}
public static void OverrideStuff()
{
IsEnabledProperty.OverrideMetadata(typeof(FormMainDataGrid), new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsEnabledChanged)));
}
public FormMainDataGrid() : base() { }
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(CanUserAddRowsProperty);
d.CoerceValue(CanUserDeleteRowsProperty);
//this was added in new version !!!
/*
if (!(bool)(e.NewValue))
{
((DataGrid)d).UnselectAllCells();
}
*/
// Many commands use IsEnabled to determine if they are enabled or not
CommandManager.InvalidateRequerySuggested();
}
}
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
FormMainDataGrid.OverrideStuff();
base.OnStartup(e);
}
}
but that does not even fire the modified version of the method.
First, am-I going the right way for this? Considering that the Deselection is caused by this method, can I completely replace the internal call to 'OnIsEnabledChanged' for my own method?
Is there another way I could be tackling this problem?
Or more specificly, how can i stop the call to the base version of this method since it is not an override, thus I cannot 'not' call the base.OnIsEnabledChanged?
Thanks alot!
For future reference, if anyone is running into the same issue.
Re-setting the SelectedValue has a lot of side-effects.
This is the correct way to override the metadata on the Grid:
public class MyDataGrid : DataGrid
{
static MyDataGrid()
{
IsEnabledProperty.OverrideMetadata(typeof(MyDataGrid), new CustomFrameworkPropertyMetadata(OnIsEnabledChanged));
}
/// <summary>
/// Fixes the issue that the DataGrid's selection is cleared whenever the DataGrid is disabled.
/// Tricky: this issue only happens for 4.0 installations, it is fixed in 4.5 (in-place upgrade) installations.
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(CanUserAddRowsProperty);
d.CoerceValue(CanUserDeleteRowsProperty);
//this is there in 4.0 dlls, not in the in-place upgrade 4.5 dlls.
//if (!(bool)(e.NewValue))
//{
// ((DataGrid)d).UnselectAllCells();
//}
CommandManager.InvalidateRequerySuggested();
}
class CustomFrameworkPropertyMetadata : FrameworkPropertyMetadata
{
public CustomFrameworkPropertyMetadata(PropertyChangedCallback propertyChangedCallback)
: base(propertyChangedCallback)
{
}
protected override void Merge(PropertyMetadata baseMetadata, DependencyProperty dp)
{
// See: http://msdn.microsoft.com/en-us/library/system.windows.propertymetadata.merge.aspx
// See: http://msdn.microsoft.com/en-us/library/ms751554.aspx
// By default, PropertyChangedCallbacks are merged from all owners in the inheritance hierarchy,
// so all callbacks are called whenever the property changes.
var thisPropertyChangedCallback = this.PropertyChangedCallback;
base.Merge(baseMetadata, dp);
// We do NOT want that default behavior here;
// The callback of DataGrid should not be called here - it clears the selection, we don't want that.
// But the callback of UIElement should be called here - it visually disabled the element, we still want that.
if (baseMetadata.PropertyChangedCallback != null)
{
Delegate[] invocationList = baseMetadata.PropertyChangedCallback.GetInvocationList();
PropertyChangedCallback inheritedPropertyChangedCallback = null;
foreach (var invocation in invocationList)
{
if (invocation.Method.DeclaringType == typeof(DataGrid))
{
// Do nothing; don't want the callback from DataGrid that clears the selection.
}
else
{
inheritedPropertyChangedCallback = inheritedPropertyChangedCallback == null
? (PropertyChangedCallback)invocation
: (PropertyChangedCallback)Delegate.Combine(inheritedPropertyChangedCallback, invocation);
}
}
this.PropertyChangedCallback = thisPropertyChangedCallback != null
? (PropertyChangedCallback)Delegate.Combine(inheritedPropertyChangedCallback, thisPropertyChangedCallback)
: inheritedPropertyChangedCallback;
}
}
}
}
Note that the issue mentioned in this post only happens in 4.0 installations without 4.5 installed.
It is 'fixed' in .net 4.5, even for applications targeting 4.0
(the "4.5 is an in-place upgrade" scenario / misery).
Regards,
Koen
I generally don't disable controls specifically for this reason. I have found it much better to either collapse the control which keeps its databinding current, or if I must keep it visible but disallow any kind of interaction, put a partially transparent black border over it that is normally collapsed and becomes visible on command.
The same problem with the Up-Down key still exists with IsHitTestVisible = false.
So what I ended up doing is re-working the custom control like this:
public class FormMainDataGrid : DataGrid
{
public FormMainDataGrid() : base() {
this.IsEnabledChanged += new DependencyPropertyChangedEventHandler(DataGrid_IsEnabledChanged);
this.SelectionChanged += new SelectionChangedEventHandler(DataGrid_SelectionChanged);
}
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs args)
{
if (this.IsEnabled)
{
_selectedValue = this.SelectedValue;
}
}
private object _selectedValue;
private void DataGrid_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs args)
{
this.Dispatcher.BeginInvoke((Action)(() =>
{
this.SelectedValue = _selectedValue;
}), null);
}
}
This works pretty well... I just have to be carefull because changing the SelectedValue when the control is disable will then put it off track...
So in conclusion, I believe that your solution is the most complete, but mine allows me to keep my form's code as lean & mean as possible.
Thanks for your help!
Say I have a global variable INT named X. Since X is global, we can assume that anything can modify its value so it is being changed everytime.
Say I have a Label control named "label". Here's what I want to accomplish:
I want to "bind" the value of label.Text to variable X. In such a way that when variable X is changed, it will be reflected back to label.Text.
Now, I don't want to write event listeners and play with delegates with this one (I want the least amount of code as possible). Is there a way to use the DataBinding component for this one? or any other novel techniques?
If you want to use the Databinding infrastructure, and reflect the changes made to a value, you need a way to notify the UI about the changes made to the binding value.
So the best way to do that is to use a property and implement the INotifyPropertyChanged interface, like this:
class frmFoo : Form, INotifyPropertyChanged
{
private string _foo;
public string Foo
{
get { return _foo; }
set
{
_foo = value;
OnPropertyChanged("Foo");
}
}
protected virtual void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Also remember that you need to setup the binding on the label first:
public frmFoo()
{
InitializeComponent();
lblTest.DataBindings.Add(new Binding("Text", this, "Foo"));
}
For a multi-threaded program (so almost every windows forms program) iCe's answer is not a good one, because it won't let you change the label anyway (you will get some cross-threading error). The simplest way to fix the problem is creating property in setter:
private string _labelText;
public string labelText
{
get { return _labelText; }
set
{
_labelText = value;
updateLabelText(_labelText); //setting label to value
}
}
where updateLabelText(string) is thread safe:
delegate void updateLabelTextDelegate(string newText);
private void updateLabelText(string newText)
{
if (label1.InvokeRequired)
{
// this is worker thread
updateLabelTextDelegate del = new updateLabelTextDelegate(updateLabelText);
label1.Invoke(del, new object[] { newText });
}
else
{
// this is UI thread
label1.Text = newText;
}
}
I don't think you'd be able to bind to a public variable. A variable by itself doesn't have the ability to notify listeners of a change in its value.
That is why you need to wrap the variable in a property. In the setter you raise an event to notify the UI controls that are bound to it, so that they can refresh and display the new value. The framework has a mechanism for this - INotifyPropertyChanged - try this link for a how-to.
Create a property for X. In setter update the label.Text property.
private int _x;
public int X {
get
{
return _x;
}
set
{
_x = value;
label.Text = _x.ToString();
}
}
I have a BindingList<> of a class set to the DataSource property of a BindingSource, which is in turn set to the DataSource property of a DataGridView.
1.
It is my understanding that any additions to the list will fire a ListChanged event which will propagate through the BindingSource and then onto the DataGridView, which will update itself to display the change. This will happen because the events have been automatically hooked up. (Yes?)
This is all fine and good when all the work is done on the UI thread, but when the list is created and changed from a non-UI thread, ultimately a cross-thread exception occurs when the grid is updated. I can understand why this happens, but no how to fix it...
2.
What I am having a tough time understanding, is where should I best intercept the ListChanged event to try and marshal things onto the UI thread? I am guessing that I need a reference to the UI thread somehow to help do this?
I have read many posts/articles on this, but I'm struggling because I don't fully understand the mechanisms at work here.
I will never be changing any items once they are in the list, only adding them, and initially clearing the list.
(I am using .NET 2.0)
You can extend BindingList to use an ISynchronizeInvoke (implemented by System.Windows.Forms.Control) to marshal the event invokations onto the UI thread.
Then all you need to do is use the new list type and all is sorted.
public partial class Form1 : System.Windows.Forms.Form {
SyncList<object> _List;
public Form1() {
InitializeComponent();
_List = new SyncList<object>(this);
}
}
public class SyncList<T> : System.ComponentModel.BindingList<T> {
private System.ComponentModel.ISynchronizeInvoke _SyncObject;
private System.Action<System.ComponentModel.ListChangedEventArgs> _FireEventAction;
public SyncList() : this(null) {
}
public SyncList(System.ComponentModel.ISynchronizeInvoke syncObject) {
_SyncObject = syncObject;
_FireEventAction = FireEvent;
}
protected override void OnListChanged(System.ComponentModel.ListChangedEventArgs args) {
if(_SyncObject == null) {
FireEvent(args);
}
else {
_SyncObject.Invoke(_FireEventAction, new object[] {args});
}
}
private void FireEvent(System.ComponentModel.ListChangedEventArgs args) {
base.OnListChanged(args);
}
}
That view is fair enough. Under the covers, other objects such as CurrencyManager and Binding make sure controls are updated when the underlying data source changes.
Adding an item to a data bound BindingList triggers a series of events that end up trying to update the DataGridView. Since the UI can only be updated from the UI thread, you should add items to BindingList from the UI thread through Control.Invoke.
I assembled a quick sample creating a Form with a DataGridView, a BindingSource and a Button.
The button spins up another thread that simulates getting a new item for inclusion in the BindingList.
The inclusion itself is done back in the UI thread through Control.Invoke.
public partial class BindingListChangedForm : Form {
BindingList<Person> people = new BindingList<Person>();
Action<Person> personAdder;
public BindingListChangedForm() {
InitializeComponent();
this.dataGridView1.AutoGenerateColumns = true;
this.bindingSource1.DataSource = this.people;
this.personAdder = this.PersonAdder;
}
private void button1_Click(object sender, EventArgs e) {
Thread t = new Thread(this.GotANewPersononBackgroundThread);
t.Start();
}
// runs on the background thread.
private void GotANewPersononBackgroundThread() {
Person person = new Person { Id = 1, Name = "Foo" };
//Invokes the delegate on the UI thread.
this.Invoke(this.personAdder, person);
}
//Called on the UI thread.
void PersonAdder(Person person) {
this.people.Add(person);
}
}
public class Person {
public int Id { get; set; }
public string Name { get; set; }
}