Ok, so I had to say something about Julian Assange and Wikileaks. I will not speculate on the probably bogus rape arrest warrant for Assange (oh, it seems I did :) ), but instead focus on one of his quotes: "If governments would prefer to not have such information surface they have two choices: don't engage in wars that even their own military employees find reprehensible, and don't rely on secrecy as a method of governance.". Sounds like the old "Nothing to hide, nothing to fear" thing, used by so many people with power to justify their actions. Well, payback's a bitch, isn't it?
Before you go on, let me summarize this long post for you: while it is possible to clone a control template in order to change just some things in it, it is a difficult and error prone process. The code at the end of the post is a proof of concept thing, which works for simple scenarios, but needs additional work for complicated controls.
I was exploring the option of not overwriting the ControlTemplate of a WPF Control when I try adding stuff to it. Instead, I tried to get the ControlTemplate and manipulate it before putting it back. It is not as easy as it seems. Even more, people stack over each other to advise everybody not to do it. I am not saying it is an easy option, so my advice is to try other alternatives, if you have them, but the idea is: it can be done!
Let's take it step by step. In order to get the control template we should get it as soon as it is available, but perhaps before applying it. One could override OnApplyTemplate and do it there or, as it is my case (trying to do it via attached properties and lacking an ApplyingTemplate event), do it once when the control is initializing. The control template is easily obtained via the Template property. If you try to change anything in it, though, you will get an exception, because the template is sealed. So the only option is to clone it, change stuff in it, then set the control template to that clone.
The template is of type ControlTemplate, but it doesn't seem to contain much. It has Resources and Triggers properties, also a VisualTree property and a LoadContent method. There is also a Template property in the ControlTemplate class... try to set it and a null exception will be thrown, so forget it. The first two are easy to use, just iterate through the collections. VisualTree is of the weird and undocumented type FrameworkElementFactory, while LoadContent is a method that returns a DependencyObject.
Well, the idea is that LoadContent will return the content of the template which you should use to set the VisualTree property, but the process of getting a DependencyObject and getting a FrameworkElementFactory tree is not simple.
First things first: get a new ControlTemplate. Its contructor gets a Type parameter which we take from the TargetType property of the original template. We then add any resources from the original template to the resources of the new one. Next step is to take the content, using LoadContent, which will get us the first child of the element tree. In order to traverse it we will use the VisualTreeHelper static class which exposes the GetChildrenCount and GetChild methods.
The next step is to create a FrameworkElementFactory. It has a constructor which receives a Type and another which gets a Type and a name string. We will use the first, since the Name can be set afterwards. The type we get from the type of the DependencyObject returned by LoadContent. The VisualTree of the new control template will have to be this new factory object, but it also needs all the properties of the original object as well as all its children.
In order to get the dependency and attached properties of each element we will use the MarkupWriter.GetMarkupObjectFor method, which returns a MarkupObject. Each of its Properties will have a DependencyProperty property which will give us the properties. However, the value of the property is not so easy to get. If we use GetValue, any binding or markup extensions will be evaluated and probably give wrong results (since the control has not been initialized yet). Using ReadLocalValue brings us pretty close, only that for certain objects like Binding we don't get a BindingBase object, but a BindingExpressionBase. We need to cast the value we get to BindingExpressionBase and TemplateBindingExpression and get to the underlying binding object.
Now that we've got the properties and the correct values, we use the factory SetValue method to set it. A special case is Name which must be set directly to the Name property. We use AppendChild to add a factory to a parent factory.
The last step is to get the Triggers from the original template and copy it in the new one. Now Seal it and you have yourself a clone. Not sure how one would manipulate the template to get a usable and maintainable template manipulation, but this is how you start.
I know you are suckers for code, so here it is:
Update:Actually the collapsed code below doesn't work except for the simplest of templates. There are several reasons for it and I will explore them below.
The first problem I found was dependency properties registered as read only that could only be set from XAML, like VisualStateManager.VisualStateGroups. When trying to use the FrameworkElementFactory SetValue method it would throw an error. Funny enough, the only reason that happened is because said method is checking if the property is read only and throws an exception. I had to use reflection to circumvent this, and it worked, albeit really ugly.
The second problem was more basic. Not every property is a dependency property. Such a simple property is Grid.ColumnDefinitions! Not only it is not a dependency property, but it is also read only. So I had to find another mechanism to fix this. At this point you probably realise this method is not a good one to employ, but if you are really desperate (or stubborn, like me) there is a way. The solution I found is to save all the properties that I need to set into a list and then set them in a RoutedEventHandler invoked from the Loaded event!
And if this is not enough, simply setting the value from the template in the control doesn't always work. In the generated template control the ColumnDefinition objects are already in the ColumnDefinitionCollection of the control. Adding them to a control that the factory generates results in an error. What I did here is a simple value=XamlReader.Parse(XamlWriter.Save(value)).
In other cases, like the Border.Child property, it must be completely ignored! So a list of properties to be ignored is needed.
Conclusion: Some improvements have been done in the code, but it's a little larger than before. The complicated way in which this works makes it cumbersome to be used, and I would not recommend it, but it works and it has extension points where errors with properties can be handled. Here is the new code:
#region Using directives
using System.Collections.Generic; using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Markup;
#endregion
namespace BestPractices { /// <summary> /// Base class for ControlTemplate transforming classes /// </summary> publicabstractclass BaseTemplateTransformer : BaseControlTransformer { #region Public Methods
/// <summary> /// Clones the ControlTemplate of a control and allows for its manipulation /// </summary> /// <param name="control"></param> publicoverridevoid Transform(Control control) { ControlTemplate template = control.Template; if (template == null) { return; } // create new template ControlTemplate newTemplate = new ControlTemplate(template.TargetType); // copy the resources foreach (object key in template.Resources.Keys) { newTemplate.Resources.Add(key, template.Resources[key]); } //get the VisualTree factory from the original template content DependencyObject content = template.LoadContent(); newTemplate.VisualTree = OnGetElementFactory(content); // copy the triggers foreach (TriggerBase trigger in template.Triggers) { newTemplate.Triggers.Add(trigger); } // allow for template manipulation OnBeforeSeal(newTemplate); // seal the template and set it back newTemplate.Seal(); control.Template = newTemplate; }
/// <summary> /// Creates a custom ControlTransformFactory for the content object. /// Override in order to replace elements in the initial template. /// </summary> /// <param name="content"></param> /// <returns></returns> publicvirtual ControlTransformFactory OnGetElementFactory(DependencyObject content) { if (content == null) { returnnull; } // use the object type ControlTransformFactory factory = new ControlTransformFactory(content, this); return factory; }
/// <summary> /// Returns a safe value for setting on the control /// </summary> /// <param name="item">The value from the template</param> /// <returns></returns> publicvirtualobject GetSafeValue(object item) { return getSafeValue((dynamic) item); }
/// <summary> /// Returns true if a property needs to be saved and set when the control loads. /// Defaults to false, except for ColumnDefinitions and RowDefinitions /// </summary> /// <param name="propertyDescriptor"></param> /// <returns></returns> publicvirtualbool MustSetProperty(PropertyDescriptor propertyDescriptor) { return sMustSetProperties.Contains(propertyDescriptor.Name); }
#endregion
#region Protected Methods
/// <summary> /// Allows for the manipulation of a control template /// </summary> /// <param name="newTemplate"></param> protectedvirtualvoid OnBeforeSeal(ControlTemplate newTemplate) { }
#endregion
#region Statics
privatestaticreadonly List<string> sMustSetProperties = new List<string> { "ColumnDefinitions", "RowDefinitions" };
/// <summary> /// Transforms a DependencyObject tree into a FrameworkElementFactory tree /// </summary> /// <param name="content"></param> /// <returns></returns> publicstatic ControlTransformFactory CreateElementFactory(DependencyObject content) { if (content == null) { returnnull; } // use the object type ControlTransformFactory factory = new ControlTransformFactory(content); return factory; }
/// <summary> /// default return the same value /// </summary> /// <param name="value"></param> /// <returns></returns> privatestaticobject getSafeValue(objectvalue) { returnvalue; }
using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Reflection; using System.Windows; using System.Windows.Markup.Primitives; using System.Windows.Media;
#endregion
namespace BestPractices { /// <summary> /// FrameworkElementFactory used in control template transformers /// </summary> publicclass ControlTransformFactory : FrameworkElementFactory { #region Nested
/// <summary> /// List of non dependency properties that will be set when the control loads /// </summary> protected List<MarkupProperty> SimpleProperties { get { if (mSimpleProperties == null) { mSimpleProperties = new List<MarkupProperty>(); } return mSimpleProperties; } }
#endregion
#region Constructors
public ControlTransformFactory(DependencyObject content, BaseTemplateTransformer templateTransformer = null) : base(content.GetType()) { mTemplateTransformer = templateTransformer ?? new NoTemplateTransformer(); // set its name string name = content.GetValue(FrameworkElement.NameProperty) asstring; if (!string.IsNullOrWhiteSpace(name)) { Name = name; } // copy the properties foreach (MarkupProperty propertyItem in getProperties(content)) { SetProperty(propertyItem); } // do it recursively int count = VisualTreeHelper.GetChildrenCount(content); for (int i = 0; i < count; i++) { DependencyObject child = VisualTreeHelper.GetChild(content, i); AppendChild(mTemplateTransformer.OnGetElementFactory(child)); } }
#endregion
#region Public Methods
/// <summary> /// SetValue that uses reflection to force DependencyProperties that were registered as read only /// </summary> /// <param name="dependencyProperty"></param> /// <param name="value"></param> /// <param name="forceSetReadOnly"></param> publicvoid SetValue(DependencyProperty dependencyProperty, objectvalue, bool forceSetReadOnly = false) { if (!forceSetReadOnly) { base.SetValue(dependencyProperty, value); } else { forceSetValue(dependencyProperty, value); } }
/// <summary> /// Sets a value from a MarkupProperty object. /// Dependency properties will be set via SetValue and the others via a Loaded handler on the object /// </summary> /// <param name="propertyItem"></param> publicvoid SetProperty(MarkupProperty propertyItem) { DependencyProperty property = propertyItem.DependencyProperty; if (property == null) { setSimpleProperty(propertyItem); } else { objectvalue = propertyItem.Value; if (value == DependencyProperty.UnsetValue) { return; } SetValue(property, value, property.ReadOnly); } }
#endregion
#region Private Methods
/// <summary> /// Force set value in the factory, even if the property is ReadOnly /// </summary> /// <param name="dp"></param> /// <param name="value"></param> privatevoid forceSetValue(DependencyProperty dp, objectvalue) { object resourceKey = getResourceKey(value); if (resourceKey == null) { updatePropertyValueList(dp, valueis TemplateBindingExtension ? "TemplateBinding" : "Set", value); } else { updatePropertyValueList(dp, "Resource", value); } }
/// <summary> /// Set a property to have its value set at load time /// </summary> /// <param name="markupProperty"></param> privatevoid setSimpleProperty(MarkupProperty markupProperty) { if (markupProperty.PropertyDescriptor == null) { return; } if (!mTemplateTransformer.MustSetProperty(markupProperty.PropertyDescriptor)) { return; } SimpleProperties.Add(markupProperty); if (mLoadHandler == null) { mLoadHandler = new RoutedEventHandler(simplePropertyHandler); AddHandler(FrameworkElement.LoadedEvent, mLoadHandler); } }
/// <summary> /// Handler on the Loaded event of the control /// </summary> /// <param name="sender"></param> /// <param name="args"></param> privatevoid simplePropertyHandler(object sender, RoutedEventArgs args) { foreach (MarkupProperty propertyItem in SimpleProperties) { PropertyDescriptor propertyDescriptor = propertyItem.PropertyDescriptor; if (propertyDescriptor == null || propertyItem.Value == null) { continue; } if (propertyDescriptor.IsReadOnly) { IList list = propertyItem.Value as IList; if (list != null) { IList destinationList = propertyDescriptor.GetValue(sender) as IList; if (destinationList != null) { foreach (object item in list) { destinationList.Add(mTemplateTransformer.GetSafeValue(item)); } } else { //shouldn't happend } } else { // what now? } } else { propertyDescriptor.SetValue(sender, mTemplateTransformer.GetSafeValue(propertyItem.Value)); } } FrameworkElement element = (FrameworkElement) sender; element.RemoveHandler(FrameworkElement.LoadedEvent, mLoadHandler); }
#endregion
#region Statics
static ControlTransformFactory() { // Get the types and methods that will be used in Reflection scenarios sUpdatePropertyValueListProperty = typeof (FrameworkElementFactory).GetMethod("UpdatePropertyValueList", BindingFlags.NonPublic | BindingFlags.Instance); sPropertyValueTypeType = typeof (FrameworkElementFactory).Assembly.GetType("System.Windows.PropertyValueType"); }
privatestaticreadonly MethodInfo sUpdatePropertyValueListProperty; privatestaticreadonly Type sPropertyValueTypeType;
/// <summary> /// get the properties set on a DependencyObject /// </summary> /// <param name="content"></param> /// <returns></returns> privatestatic IEnumerable<MarkupProperty> getProperties(DependencyObject content) { MarkupObject markupObject = MarkupWriter.GetMarkupObjectFor(content); return markupObject.Properties; }
/// <summary> /// Get the ResourceKey property from an object, if it exists /// </summary> /// <param name="target"></param> /// <returns></returns> privatestaticobject getResourceKey(object target) { if (target == null) { returnnull; } Type type = target.GetType(); PropertyInfo property = type.GetProperty("ResourceKey"); if (property == null) { returnnull; } return property.GetValue(target, newobject[] {}); }
This book is different from the books I usually read because it is an autobiography. However it has enough science in it to be great, enough fantasy in it to be totally inspirational and also it is one of the most real (and thus sad) books I have ever read. What is even nicer is that the book is free online on Anthony Zuppero's site. I can't recommend it enough. Go there, download it, read it: To Inhabit the Solar System
The plot itself is about this physicist guy, diagnosed with Asperger's syndrome, who gets into his head that we could build rockets to go to other star systems. It all starts in 1968, when he reads about the Dyson nuclear bomb propulsion, an outrageous scheme to detonate nuclear bombs to push a rocket. From then on, he embarks into jobs that are closer and closer to this purpose, always finding solutions to problems that appear along the way. In the end, he finds a way to cheaply get the water from comets, asteorids, moons and use it to propel spaceships around the Solar System. He practically gives us the keys to the universe, the highways that would allow the thorough exploration and utilization of resources in our solar system. It is just amazing.
There are multiple things that I liked about the book. Most of all, I liked the guy. He is what I would call a true hero: he finds a cause and dedicates his life to it, without any desire for personal gain. He doesn't just blab around about the ideas that he has, he finds people, resources, makes calculations and determines the problems that arise and specific concrete solutions for them. Then the style of the book: so bloody honest, so many things to be learned from the way he repeats what is important, the details of all his thoughts, hopes and desires; a great read. And last, but not least, the technical aspects of the book. After reading it, you will be able to understand each step of getting fuel and construction material from space, using it to propel and build stuff, all in a reasonable enough price and without the need for expensive planet-space trips.
Now, there are some issues with the book. First of all, it is not at all polished. It says its story, but it's also filled with personal notes, incomplete chapters and various information. My guess is that at some time he wanted to publish the book and no one was interested. Or maybe he just didn't want to waste time polishing the book and stop people from getting the ideas in it. Or maybe he just didn't feel the story ended. Either way, for me it added to the charm and realism of the book, rather than take stuff away.
It was heartbreaking to read about the death of Gene Shoemaker. In the book - the author took it hard - but it so happened I was reading the book while they announced the death of Brian Marsden, another proeminent character in the book, and I felt the pain anew.
Bottom line: you should read this. If not for the quality of the book, not for the realistic description of government agency inner workings and personal tricks to get something done, if not for the amazing person that Anthony Zuppero is, read it for the detailed description on how we could today (actually, from about the 1980's) inexpensively inhabit the Solar System.
In order to programmatically load a .crx Chrome extension as an external extension, the ID of the extension is required. The algorithm for computing it is:
make a SHA256 hash of the public key embedded in the .crx file
take the first 128bits (16 bytes) and encode them in base16
use characters a-p instead of the customary 0-9,A-F
For this we need, obviously, the public key. Reading from the CRX Package Format page, we can determine we need a 4 byte (Int32) value of the public key length and the public key itself. The length is found at position 8 in the file, the public key starts at position 16. Here is the code:
privatevoid checkPath(string path) { mUri = ExtensionHelper.GetUri(path); if (mUri == null) { thrownew Exception(string.Format("Parameter is not a valid URI ({0})", mPath)); } mPath = mUri.AbsolutePath; if (!mUri.IsFile && !mUri.IsUnc) { thrownew Exception(string.Format("Only file and local network paths are acceptable ({0})", mPath)); } DirectoryInfo di = new DirectoryInfo(mPath); if (di.Exists) { thrownew Exception(string.Format( "Loading extensions from folders is not implemented ({0})", mPath)); } FileInfo fi = new FileInfo(mPath); if (!fi.Exists) { thrownew Exception(string.Format("The file does not exist ({0})", mPath)); } if (fi.Extension.ToLower() != ".crx") { thrownew Exception(string.Format("The file extension must be a .crx file ({0})", mPath)); } try { mExtractor = getExtractor(fi); if (mExtractor.Check()) { return; } } catch (Exception ex) { thrownew Exception( string.Format("The file could not be read as a valid .crx file ({0})", mPath), ex); } thrownew Exception(string.Format("The file could not be read as a valid .crx file ({0})", mPath)); }
I was trying to verify an SHA256 signature (don't ask) and so I had to use the class SHA256CryptoServiceProvider. Alas, the constructor promptly threw an exception telling me the class is not supported on my system. I googled it and found this answer: C# - SHA256CryptoServiceProvider and related possible to use on WinXP?. Whereas the link reports this is a bug in .NET 3.5 I am working with version 4.0 and it is still there.
Apparently, the constructor of SHA256CryptoServiceProvider looks for a certain name in the cryptography providers installed on the machine. It looks for "Microsoft Enhanced RSA and AES Cryptographic Provider" and it gets the XP variant "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)". The solution is to export the key, remove "(Prototype)" and install the resulting reg file. Then it works. Here is the file, as it resulted on my computer:
Update: in order to execute a script in any open tab inside Chrome, use the executeScript method of the tabs API.
Chrome browser extensions are so cool, that they protect you from accidentally running and adding script to a page that might interfere with the original code of the page. Unfortunately, they don't give you a parameter or option to actually do that, if it is what you actually want. All you can do, by default, is to access the elements in the page document, but not change direct members of window or even document.
I had this control that consisted of a textbox and a squigly red line in case of a required value error. In order to do it, I added the textbox and the line to a Grid, without specifying column or row values, so that the line would come above the textbox. It all worked well until I wanted to make the control align to the left, thus growing with the content entered in it. To my surprise, the control would stretch to the content width when in edit mode and go to 202 pixels outside it. The only things in the template were a textbox and a line inside a grid and a border, so I proceeded on inspecting all of their attributes in search for the culprit. It so happens that the Line was it!
Update: The Microsoft guys responded in a day to my bug report, but they couldn't reproduce it. It seems this behavior can be reproduced only in a Grid column with Width="Auto". Frankly, I was a bit surprised to see that, lacking that grid column, a line with Stretch="Fill" and HorizontalAlignment to "Left" would still expand the container to its maximum size. End update. The code looked like this:
As you can see, the line is Left aligned, it has no specified Width, the only giveaway is the Stretch property set to Fill. Now, you think that was the problem, but it was not! See that I have a MaxWidth of 200. That was per request.
It appears that if I remove the MaxWidth setting, the line goes DOWN to the normal size of the parent inner width. MaxWidth, not MinWidth, mind you. Ok, so I've tried some other things. Stretch to None makes the line be 1px long. Setting X2 to 200 makes the line take 200px, same as setting Width. HorizontalAlignment to Stretch makes the line go to the center of the space if it is bigger than the 200 MaxWidth.
The solution? I've bound the Width of the line to the ActualWidth of the TextBlock above. Another option would have been to surround the line with a scrollviewer or some other control that would allow the line to be as long as it wanted without showing a scrollbar or stretching to the size of its content. Either solution seems bad.
Is this a bug? I think so. If the Stretch property should have affected the space the line takes, then it should have done so when MaxWidth was set to Infinity, but it did not. Well, hopes it helps someone. Final code piece for the line:
Update: The fix I've exemplified above doesn't work when the HorizontalAlignment of the grid is Stretch or has a Width and the TextBox doesn't have a Left HorizontalAlignment. I have tried to replace the Line with a ScrollViewer with hidden scrolling on the horizontal and disabled on the vertical and having a MaxWidth of 200, Inside placing the troublesome Line. I've tried all kinds of panels and combinations of HorizontalAlignment, HorizontalContentAlignment, ClipToBounds, etc. All to no avail.
Finally, the solution was to create a simple control that would ignore the dimensions of the child controls, demanding no space. I named it ClipContainer and here is its source:
Sometimes, when working with a WPF application, Snoop would crash with the most innovative error messages possible. One of these was 'Object' type does not have a matching DependencyObjectType. Well, of course it doesn't, but where does this come from, why only when I use Snoop and how do I fix it?
I won't bore you with the details, enough said I have traced the problem to an AttachedPropertyBrowsableForTypeAttribute I have used on an attached property. I was using a Resharper Live Template (a snippet) to generate the code for the property and I'd accidentally forgot to set a proper type in the attribute and left the default object.
I had this container that I wanted to handle any mouse click events. So I proceeded on creating an attached property that has a property changed callback in which I would take the element and add a mouse handler to it. Pretty standard stuff, only it didn't quite work. It also blocked any events in the children. As I knew this should have happened in Preview events, not in normal events, I was stumped.
The problem: the attached property was defined with FrameworkPropertyMetadataOptions.Inherits which meant its value applied to all the children, meaning the value changes on all children when I set it on the parent. That meant the handler for the mouse click events was attached to the container and each of its descendants.
Here is an unsettling news: US and Indian filmmakers sign Hollywood-Bollywood deal. In my mind, this means outsourcing to India for movies just as good as the software coming from there, it means working together to control distribution and selection of movie material, coordinating moves so that the huge garbage spewing movie monster we now call Hollywood would have no competitor, ever.
Maybe I am just paranoid, but where are the Internet based movie-hacker studios that should have sprouted everywhere with low budget, but very cool films? Do they all stop at small stuff on YouTube and then get a job in fast-food? Where is the "free market" competition in entertainment?
I was comparing these two MySQL queries. They looked the same, except for some extra joins and groups, but one was lasting for 5 seconds, the other for 2 minutes. I removed all extra stuff from the long lasting query to get to an almost identical query as the first, only to see it run for two minutes again. The only difference was the type of a WHERE clause. Instead of comparing date with "20101012" (string) I would compare it with 20101012 (integer). Apparently, the type conversion alone was invalidating any use of the index on the date column and made the query last forever. Good to know.
The scenario is that an Image that has its Stretch property set to None still has a different size from the original image source. You check the Padding, the Margin, the VerticalAlignment and HorizontalAlignment properties, you play with the RenderOptions and SnapsToDevicePixels and nothing seems to work. You specify the Width and Height manually and the image is clipped. The problem here, believe it or not, is that the DPI setting of the image source is different from the system DPI setting.
The scenario is easy enough to create: make a Brush, use a Binding as the Color property, use your brush in your app, then start the application and change the system theme. An error "This freezable can not be frozen" will be triggered. The solution is to set x:Shared="False" for the brushes with color bindings.
Update February 2016: The Microsoft Connect page has disappeared. Maybe it was because the bug was marked as "closed - by design", or some other Microsoft site revamp idiocy.
The Void Trilogy ends with The Evolutionary Void in a typical Hamiltonian way: completely off the scale science and fights, actions with galactic and universal implications and the bunch of special heroic people that lead the entire story to a climactic finish.
I couldn't wait for the last book of the trilogy to get out and I finally got hold of it, but more than a year had passed since reading the first two. Most of the characters I had to remember while reading the book, something that degraded a bit the reading experience. Take it as a hint: before starting a Peter F. Hamilton series of books, make sure they are all available before you start, as you can't let them out of your hands until you get to the end and the feeling of loss is horrible.
Now, about the book itself. The middle of the galaxy hosts an all devouring and unstoppable Void, inside which thought is the main law of physics and which feeds on the mass of the worlds outside in order to sustain itself. Basically, the heroes in the book are battling galactic cancer. The style of the narrative mixes incredibly advanced technology with an archetypal feudal heroic fantasy, bringing them flawlessly together at the end. Not everything makes sense, but then again, not everything could. Simple solutions to problems were available, but never explored, and some characters were popping in and out of the book stream like so many quantum fluctuations. But on the whole, it was a great reading, keeping me connected for the entire length and, unexpectedly judging by the Hamilton books I have read, with a good, satisfying ending.
Now, I plan on reading some non fiction books, then I will probably return to the Prince of Nothing universe. After that, who knows?
I had this control where a button was displaying a ContextMenu. I wanted to keep the ContextMenu open so I can manipulate its content. I had assumed that the StaysOpen property would do that for me; it did not. Also, I tried using a ContextMenuClosing event approach, only to discover that it is one of those rare "ing" events that doesn't have a Cancel property. I've looked in the sources of ContextMenu and Popup to see just what is going on and I have decided that the design was impossible to patch in order to get the behaviour I wanted.
In the end, the only solution I could find was to inherit from ContextMenu into a new class that would coerce the IsOpen property to true when StaysOpen is set to true. That did the trick. Here is the code:
privatestaticobject coerceIsOpen(DependencyObject d, object basevalue) { ContextMenu menu = (ContextMenu)d; if (menu.StaysOpen) { returntrue; } return basevalue; }
}
Hope it helps people out.
Update:
This solution only works for the body of the ContextMenu, the submenus are something else. However, the submenus are defined in the control template of a menu item, so that can be easily remedied by changing it to your needs. One quick and dirty solution would be to add a trigger that sets the IsSubMenuOpen property to true whenever StaysOpenOnClick is set. Or, if you simply want to freeze a menu in place, change the template so that the mouse click or hover will only trigger IsSubMenuOpen when the parent ContextMenu has StaysOpen to false, while the StaysOpen property of the MenuItemPopup is set to the ContextMenuStaysOpen.