WeakEventManager & DependencyPropertyChangedEventArgs - wpf

I am wondering what might be the best way to use the WeakEventManager (4.5 is fine) together with Events offerring DependencyPropertyChangedEventArgs. These do not derive from EventArgs (for performance reasons) and therefore WeakEventManager does not work out of the Box.
Any guides, links or tips would be highly appreciated!

I'm not sure how using 'PropertyChangedEventManager' would resolve the issue regarding 'WeakEventManager' and binding weak event handlers that use 'DependencyPropertyChangedEventArgs'.
The 'PropertyChangedEventManager' works with instances of 'PropertyChangedEventArgs', which is derived from 'EventArgs', where 'DependencyPropertyChangedEventArgs' does not. This is why standard methods don't work.
In cases like this you can always use a manual approach ('WeakEventHandler' is declared within the scope of the 'MyType' class):
private class WeakEventHandler
{
private readonly System.WeakReference<MyType> m_WeakMyTypeRef;
public WeakEventHandler(MyType myType) => m_WeakMyTypeRef = new System.WeakReference<MyType>(myType);
public void OnClientIsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs args)
{
if (m_WeakMyTypeRef.TryGetTarget(out var myType))
myType.OnClientIsKeyboardFocusWithinChanged(sender, args);
}
}
And code to bind (from within 'MyType' method):
var weakEventHandler = new WeakEventHandler(this);
frameworkElement.IsKeyboardFocusWithinChanged += weakEventHandler.OnClientIsKeyboardFocusWithinChanged;
The downside is that you have to declare a new (private) class although the same class could handle multiple events.

Use the PropertyChangedEventManager built in to .NET.

Related

How to retrieve the WebBrowser control from the HTMLDocument it contains, in WPF?

My WPF application is creating multiple WebBrowser controls. I know how to manipulate the HtmlDocument within each and also how to handle mouse events on them.
However, from within a mouse event which has a IHTMLEventObj2 object as parameter, how can I retrieve the hosting WebBrowse?
I can get to the document through the srcElement.document but how do I 'navigate up' to the WebBrowser that is hosting this document?
I thought of using a 'Tag' property, but HTMLDocument does not have one.
As a last resort, I probably could use a hash table based on the HtmlDocument object, but this is a bit complicated for such a simple thing ...
Where/how do you get your mouse event and srcElement.document? It seems like javascript.
If true, then I'm pretty sure you can't access the web control from JavaScript, because the web control is not exposed within the DOM tree. You could try to use window.external (or similar) and to expose methods through it, and then have the methods operate on the webbrowser, but that'd be a little convoluted, but I'm sure this way it is possible.
If not true and if you have some mouseevent handler in C#, then simply link the handler with the webbrowser before the event is invoked. Instead of:
// inside your Window/etc:
private int otherData;
private void MyHandler(...args) {
if(otherData > 5)
browser.Navigate("foobar.html");
}
WebBrowser wb = ...;
wb.themouseevent += myhandler; // equivalent to wb.themouseevent += this.myhandler;
use closures or custom objects to expose a handler from an object that will "know" the browser beforewards:
// inside or outside your Window/etc:
class MyHandlersWithSomeData
{
public WebBrowser browser;
public string someContextuaData;
public int otherData;
....
public void MyHandler(...args) {
if(otherData > 5)
browser.Navigate("foobar.html");
}
}
// inside your Window/etc:
WebBrowser wb = ...;
var smartHandler = new MyHandlersWithSomeData{ browser = wb, otherData = 10 };
wb.themouseevent += smartHandler.MyHandler; // note that handler is not from "this" anymore
edit: As you asked, a "simpler" approach would be to use lambdas and closures:
// inside your Window/etc:
private int otherData;
private void JustAMethodNotAHandler(WebBrowser browser, object sender, EventArgs args) {
if(otherData > 5)
browser.Navigate("foobar.html");
}
WebBrowser wb = ...;
wb.themouseevent += (sender, args) => JustAMethodNotAHandler(wb, sender, args);
However there is no magic. Under the hood, it does it almost exactly as the example above with an extra class, so called "closure". This class will store the reference to WebBrowser wb local variable and only thanks to that, when JustAMethodNotAHandler is later called, the wb is still available and passable to that method.
However, since we are now using lambdas ((blah)=>blah syntax) to quickly create the delegate, you must notice two very important things:
JustAMethodNotAHandler is not the handler, it is just a method. The anonymous function created by the lambda will be the actual handler
since the anonymous function is, well, anonymous, you will have a hard time if you ever want to unregister it later. Attempts like:
wb.themouseevent -= (sender, args) => JustAMethodNotAHandler(wb, sender, args);
will not work since each time that line is executed, a new handler is created, totally not equal to the one created with +=

Subscribe PropertyChanged events of window in C++/CLI

I just tried to subscribe to WPF property change events using C++/CLI. I didn't expect this to get difficult.
First I tried to subscribe to a specific property of some window (IsMouseDirectlyOver) and finally succeeded with following code:
void MyClass::DependencyPropertyChanged(Object^ sender, DependencyPropertyChangedEventArgs args)
{
Debug::WriteLine("DependencyPropertyChanged: "+sender->ToString()+", "+args.Property->Name);
}
window->IsMouseDirectlyOverChanged += gcnew DependencyPropertyChangedEventHandler(this, &MyClass::DependencyPropertyChanged);
Then I tried to subscribe to any property changes of an object (which is most important to me because my final code must be able to handle property changes by property names). I totally failed on this.
I tried various things but nothing worked. I could not find any C++/CLI examples but according to documentation and C# examples the following seemed to be the most sensible code to me:
window->PropertyChanged += gcnew PropertyChangedEventHandler(this, &MyClass::PropertyChanged);
void MyClass::PropertyChanged(Object^ sender, PropertyChangedEventArgs^ args)
{
...
}
But the compiler tells me by error C2039 that 'PropertyChangedEvent' is no element of 'System::Windows::Window'.
How can I achieve what I want?
Al mentioned in the comments, your code doesn't work, because there is no PropertyChanged event on Window, it's as simple as that.
What you can do instead is to override the OnPropertyChanged() method, which is present on a Window. In your override, you can do anything you want, including raising PropertyChanged (don't forget to create that event first).
I had a look on the snoop sources. I modified it and wrote a very, very basic example that works:
String^ ownerPropertyName = "IsActive";
DependencyObject^ propertyOwner = window;
DependencyPropertyDescriptor^ ownerPropertyDescriptor = DependencyPropertyDescriptor::FromName(ownerPropertyName, propertyOwner->GetType(), propertyOwner->GetType());
DependencyProperty^ ownerProperty = ownerPropertyDescriptor->DependencyProperty;
Type^ ownerPropertyType = ownerProperty->PropertyType;
DependencyProperty^ myProperty = DependencyProperty::Register(ownerPropertyName, ownerPropertyType, GetType(), gcnew PropertyMetadata(gcnew PropertyChangedCallback(&MyClass::BoundPropertyChangedCallback)));
Binding^ myPropertyToOwnerPropertyBinding = gcnew Binding(ownerPropertyName);
myPropertyToOwnerPropertyBinding->Mode = BindingMode::OneWay;
myPropertyToOwnerPropertyBinding->Source = propertyOwner;
BindingOperations::SetBinding(this, myProperty, myPropertyToOwnerPropertyBinding);
And:
static void BoundPropertyChangedCallback(DependencyObject^ me, DependencyPropertyChangedEventArgs args)
{
Debug::WriteLine("BoundPropertyChangedCallback: "+args.OldValue+", "+args.NewValue+", "+args.Property->Name);
}
Looks pretty complicated to me. I have no idea if that binding stuff is really necessary. In fact this can even subscribe to properties that do not have events (like IsMouseOver) and can operate on objects that do not implement INotifyPropertyChanged (like Window). And it does not need any switch/case for properties.
The class PropertyDescriptor (or the derived DependencyPropertyDescriptor) provides a mechanism to add a property change handler by their AddValueChanged method:
DependencyPropertyDescriptor^ propertyDescriptor = DependencyPropertyDescriptor::FromName(
"ActualWidth", component->GetType(), component->GetType());
propertyDescriptor->AddValueChanged(component, gcnew EventHandler(ActualWidthChanged));
...
static void ActualWidthChanged(Object^ component, EventArgs^ e)
{
...
}
Unfortunately the handler doesn't get passed the changed property, so i guess you would have to add different handlers for all properties you want to monitor.
EDIT: You might implement something like the code shown below that uses an anonymous delegate to pass the property name to an appropriate handler. Note however that this is C#, and to my understanding this can't be done in C++/CLI, since there it does not support managed lambdas. Mayby you could wrap a helper class like this in a separate assembly and use it from your C++/CLI code.
public delegate void PropertyChangedHandler(object component, string propertyName);
public static class DependencyPropertyDescriptorExt
{
public static void AddPropertyChangedHandler(
this object component, string propertyName, PropertyChangedHandler handler)
{
var propertyDescriptor = DependencyPropertyDescriptor.FromName(
propertyName, component.GetType(), component.GetType());
propertyDescriptor.AddValueChanged(component, (o, e) => handler(o, propertyName));
}
}
Now you could write and use such a PropertyChangedHandler like this:
this.AddPropertyChangedHandler("ActualHeight", PropertyChanged);
...
private void PropertyChanged(object component, string propertyName)
{
...
}

C# WinForms Adding ToolStripMenuItem dynamically. Why does this not work?

I have created an instance of a ToolStripMenuItem and wanted to add it as a submenu to two different menus on my form (to a contextmenu and a menu strip). I know how to get it to work but I am wondering why this doesn't work.
private static string[] parameters = { "itemOne", "itemTwo", "itemThree"};
private void MainForm_Load(object sender, EventArgs e)
{
foreach (string s in parameters)
{
ToolStripMenuItem addThis = new ToolStripMenuItem(s);
existingToolStripMenuItem.DropDownItems.Add(addThis);
existingMenuItem.DropDownItems.Add(addThis);
}
}
I noticed it works fine if I comment out one of the DropDownItems.Add() statements or if I create two separate instances. Why does it do this?
If you learn about the implementation of ToolStripItemCollection.Add, you will find that the second call existingMenuItem.DropDownItems.Add(addThis); removes addThis from existingToolStripMenuItem.DropDownItems.
So learning how to use decompilers such as ILSpy is critical for .NET developers,
http://wiki.sharpdevelop.net/ilspy.ashx
A possible workaround is to create two separate instances as you found out. If you intend to connect the two instances together, you can use ActionList,
http://www.lextm.com/2012/04/packaging-crads-actionlist-for-net-via-nuget/

How to use parameterless constructor with ninject di and winforms

I'm working on a WinForm application using ninject for dependency injection. My first problem was that the form being instantiated had a parameter (for DI). I added a parameterless constructor thinking this would help. The problem now is that the code inside the constructor with the parameter gets skipped. Here what it looks like:
On my main form:
private void mnuSettings_Click(object sender, System.EventArgs e)
{
frmSettings objForm = new frmSettings();
objForm.Owner=this;
objForm.Show();
}
In the frmSettings form:
private readonly IApplicationPropertiesInterface _applicationProperties;
public frmSettings()
{
InitializeComponent();
}
public frmSettings(IApplicationPropertiesInterface applicationProperties) : this()
{
_applicationProperties = applicationProperties;
}
When I call _applicationProperties.GetExtractFileSaveLocationDirectory() it blows up because the code to set _applicationProperties was never called.
Im wondering if I have structured this incorrectly, and what the best way to achieve this is. My goal is to call the parameterless constructor, but also set _applicationProperties.
Any assistance would be most grateful.
I'm guessing you might be expecting that having Ninject in the building will mena that new will work differently to normal. It doesn't - you need to be doing a kernel.Resolve<Something> for the DI to kick in. Note that most of these pitfalls are covered in detail on the wiki
Can you edit your answer to include details of what you're doing outside of this form please?
In the meantime, here are some previous questions that overlap significantly:-
What is the best practice for WinForms dialogs with ninject?
How to use Ninject in a Windows Forms application?

wpf behavior unit test

I am using an attached Behaviours to add drag and drop functionality to my code.
So far, everything is working fine, but my problem is when I want to test my behaviour classes.
For example, one of the behaviour classes would be something like the following:
public class DroppableContainerBehavior: Behavior<FrameworkElement>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.AllowDrop = true;
AssociatedObject.Drop += new DragEventHandler(AssociatedObject_Drop);
AssociatedObject.DragOver += new DragEventHandler(AssociatedObject_DragOver);
AssociatedObject.DragLeave += new DragEventHandler(AssociatedObject_DragLeave);
}
private void AssociatedObject_Drop(object sender, DragEventArgs e)
{
...
}
}
My problem now is when I want to create a unit test for the AssociatedObject_Drop method, i would need to create a DragEventArgs object, but this class is sealed.
I got the impression that I am doing something wrong..
My question is, should i be testing my behaviour classes? Behaviours are related with UI, and usually it's not worth it to test UI. Am i right?
Maybe I have to change my behaviours code to make it more testable? any ideas?
Thanks for your help!
I would refactor the code and move out any business logic from AssociatedObject_Drop into its own function(s) and then write my unit tests for those functions.
you can create an object even its class is sealed.
you can test the raise Drop() event in your unit test
you also can test the AssociatedObject_Drop() method logic by extracting its code to other function and write the unit test for this function.

Resources