I've recently developed a drawing component for my company, featuring a Canvas on which you can draw certain shapes using click-and-drag. For each shape, I placed two adorners on its AdornerLayer: One for increased hit detection (basically a transparent rectangle that would exceed the shape's boundari开发者_Go百科es by a few pixels), and another for resizing (four Thumb controls on the corners).
But, I ran into many problems when implementing some of the features of the component, all adorner related.
They captured all preview events, since they were in another visual tree than the Canvas itself, that was unexpected, but I found a workaround, even if I did not like it that much. Using an AdornerDecorator did not solve this issue, a selection adorner as I implemented is a black hole for preview events.
When I implemented z-index manipulation of shapes on the Canvas (send to back, bring to front, etc), it worked fine using Panel.SetZIndex, as you'd expect. But, the adorners are in another visual tree! so they were not affected and the selection adorners were STILL on top of all other shapes, even if those shapes were on top of the one the selection adorner was detecting hits for. For example: Shape1, SelectionAdorner1. Shape2, SelectionAdorner2. Shape1 is on top (added later to the canvas) of Shape2, so it's overlapping it. So a click on it will be detected by SelectionAdorner1. I manipulated the ZIndex to send it to back, now Shape2 is on top and overlaps Shape1. I click on top of Shape2, but the click is detected by SelectionAdorner1 instead of SelectionAdorner2. This was particularly annoying. So, apparently since Adorners are on another visual tree, they don't respect ZIndexes. I tried to solve it by creating a DataBinding (and also by manually setting) between the ZIndex of the shape and the ZIndex of its SelectionAdorner. But this did not solve the issue. Altering the ZIndex of Adorners did not affect the way they were displayed on screen, maybe I was missing something, but it shouldn't be really this hard, since Adorners are supposed to be made to make things easier. The only solution I could come up with was manually removing all adorners and add them again manually one by one, adding for last the one that was supposed to be on top. That was retarded, but it worked.
Next, Adorners don't respect ClipToBounds! I set ClipToBounds=true in the Canvas I was doing my drawing on, and it worked fine, but damn adorners would still work! The solution to this was relatively painless, I just added an AdornerDecorator on top of each Shape. Not an ideal solution IMO, but one that was simple enough.
Adorners not always react well to LayoutTransforms performed on their adorned elements. I have a Panel on top of the canvas which implements Zoom and Pan functionality. It used Animations to make zooming in and out more smooth. But using animations caused my Adorners to go ape! The first zoom they would simply ignore the resize and stay the same size and position, on the second zoom they would scale to the previous size of the adorned element. That did not make any sense! only solution I could find was to disable animations, which thankfully worked
I don't quite remember which other issues I had, but this was more than enough to make me wonder about the usefulness of Adorners, and I'm seriously considering not using them in my next project, which is similar to the one I described.
So, can anyone tell me what could possibly be the pros of using these seemingly useful but incredibly annoying things?
I think you already know the answer to your question. They save time in some aspects, and cause problems in others. If you were to code this designer behavior using a variety of UserControls, you would find yourself writing a lot of boilerplate control classes to wrap the elements that you actually wanted to edit. If, on the other hand, you tried to write separate editing controls and intelligently overlay them, you would be writing boilerplate code to keep their positions and sizes in sync. The approach you took, using adorners, resulted in a lot of (fairly boilerplate) code to manage events.
While adorners may not be the best tool for this particular task, they are still a useful tool for other, simpler tasks. I recently wrote a similar kind of "design surface" and adorners were a godsend for two pieces:
- The drag-drop behavior. When I dragged different elements, they needed to have different visual previews; this was remarkably easy to accomplish using a custom adorner and data templates.
- The selection rectangle or "lasso." You can see something similar when you hold your left mouse button down on a Windows desktop and drag the pointer around. It creates a semi-transparent box that can select multiple elements. I was able to create this behavior almost instantly with the adorner layer, whereas creating my own custom control resulted in a lot of unnecessary book-keeping.
I think what you discovered in your project is that you may have been using adorners to try and accomplish too much. But don't throw the baby out with the bathwater-- they are still very useful in certain scenarios.
精彩评论