Creating custom WPF layout panel - ignored children not disappearing - wpf

Good afternoon. I'm creating a custom WPF layout control that needs to omit children from the measure/arrange process if they won't fit in the available area. I'm testing with Blend.
I have tried overriding GetVisualChild(n) and VisualChildCount to try and allow the runtime to only recognize certain elements as my desired children. This seems to work okay, but the big issue I'm having is this:
I drop some children into my layout control from Blend. I then shrink the control where some children won't fit inside my panel. The issue is that the children that are no longer in view remain rendered on the Blend design surface. I have tried invalidating their visual, calling "RemoveVisualChildI()".. but nothing seems to want to make the rendered visuals disappear.
Breaking into the GetViasualChild(...) method, I am witnessing Blend and the runtime are only asking for the actual visible children (fully contained inside my layout panel). So, it seems to me that the problem is tied to the child elements thinking that they still need to be rendered..or that the container panel just doesn't know it needs re-rendering.
Any clues? I can try and provide more detail if it's needed, but my basic need is the ability to selectively hide/show (omit from the measure/arrange process) certain children of a custom panel.
Thanks!
- Sean

Since nobody has responded, I figured I would share the solution. The visual tree is established through the AddVisualChild(...) method on the Visual base class. The Panel takes care of this for you with it's Children collection. To solve this, all I needed to do was create an instance of a UIElementCollection, and let it do the dirty work. When that class is created, it is passed a visual parent and an optional logical parent. When elements are added to that collection, the parent / child relationship gets established automatically.
Hope this helps the next person!

Related

LayoutUpdated event has stopped firing. Handlers are in place, any ideas?

I have a control which allows the user to add and manipulate n graphical and text layers. The layers are each usercontrols that are added to the parent control's canvas. I use the parent control's LayoutUpdated event to refresh transform specific variables and other stuff. With no change to the handler method, the LayoutUpdate event stopped firing. I've obviously done something that caused it, but I haven't been tinkering with anything that can logically tie to this new problem. Any ideas are appreciated - thanks.
JUST TO CLARIFY: I'm not asking for a solution - just an anecdote of experience with something similar - That will probably be more helpful than you can imagine. Thanks
The cause, in this case, was a WPF UserControl (kid) defined in xaml at design time inside a canvas without any dependency property values set. The canvas is inside another usercontrol of different type (mom). At runtime, a mom is instantiated and her kid is sized, positioned, and made visible within the canvas of mom based on the runtime size of mom as well as calling arguments that denote whether or not kid is to be visible. This instance of kid is only sized and positioned if it is requested to be visible, if not, it is only hidden (not sized, not positioned).
The presence of this instantiated, but unsized and unpositioned element within the canvas of mom was causing the failure of layoutupdated events to fire. This situation effected not only the instance in question, but all other instances created at runtime in the project afterward. (NOTE - no exception was raised)
The solution appropriate for this circumstance was to explicitly size and place this element within the canvas - even if it is not needed. The layoutupdated events then fire as expected. I would love to provide a more profound and generalized answer, but perhaps wiser people can include their insight as well. Thanks

I need advice on how to design/implement this custom panel

I've written a custom panel which displays its children docked either vertically or horizontally, separated by moving splitters in between. Since the Grid panel offers much of this functionality out-of-the-box, I just inherited from it.
To create the layout, upon Loaded is fired I do the following:
1) Read how many children it has and create the appropiate number of rows/colums.
2) Position every existing children in the corresponding row/colum.
3) Create, position and add a GridSplitter for every child.
This approach looks and works fine, but it opens the door to a lot of problems:
Since it's added a GridSplitter for each child, there are twice the number of expected children. If someone added 3 elements to it, Children.Count would return 6.
User could insert/remove things at the wrong place.
It just throws an exception when this Grid is used as the ItemsPanel for an ItemsControl, since in this case WPF (not Silverlight) does not allow direct children manipulation.
These 3 cases are the ones I've already tested, but I'm pretty sure a lot more would arise depending on what the user does with it.
As it turns out, this class must be regarded as 'implementation details', so the real question is, what control should I put in front of the user?
It sounds like it should be a Panel, but I can't control the Children property since it's not virtual, and there's also the ItemsControl which I think could be a good candidate, but I really don't know.
I'd much appreciate any kind of advice or some directions to do this the right way.
Thanks in advance.
You see using just grid you leave yourself with an imperative way of adding items only. As in
myCustomGrid1.AddMyItem(***), Grids simply don't have ItemsSource property. ItemsControls do - so if you need support for declarative items sources i.e. myControl.ItemsSource = {Binding ...} you're going to derive your control from ItemsControl. This is not a two liner - making your ItemsPanel Children writable is a big challange - there's no simple way of doing that.
This is all about a small thing overlooked during the Grid's design - splitters shouldn't have been added to Children collection, as Children are visulaizations of your BOs while spliiters are just formatting elements.
Here's what I would do.
Forget about ItemsSource & items altogether - it's aint worht the hassle. The only way to add/remove items to your control will be AddResiazableItem/RemoveResizbleItem. Calls will add items and splitter (for the middle items), extend the number of rows/cols of your grid depeneding on its orientation, set Grid.Row/Grid.Column attached properties for your visual children. You can keep your actual objects internally to support Orientation change.
If at any stage you'll want to bind your control to IEnumerable source - just create an attached behavior, which will iterate through the items and call AddResiazableItem within a loop.
Cheers.
P.S. To moderators - the editor seems to get broken, lads. I cant see the second item.
P.S.S. Got it fixed after a few tries.

Can I change the Thread Affinity (Dispatcher) of a Control in WPF?

I want to create a control which takes a while to create (Pivot) and then add it to the visual tree. To do this i would need to change the dispatcher of the control (and its heirachy) before adding it to the VisualTree.
Is this possible? Are there any implications of walking the controls trees and setting the _dispatcher field via reflection?
AFAIK this only works with Freezable derived classes. The best solution I see is to create the control on the UI Thread and show a progress bar during creation. To make this possible you will have to create the control in portions an let the progress bar update itself once in a while. This not only necessary for the progressbar but also will make sure that you application does not block.
Pseudocode (execure in extra thread):
this.Dispatcher.BeginInvoke(UpdateProgress(0));
this.Dispatcher.BeginInvoke(bigControlBuilder.Build(0,25));
this.Dispatcher.BeginInvoke(UpdateProgress(25));
this.Dispatcher.BeginInvoke(bigControlBuilder.Build(25,50));
this.Dispatcher.BeginInvoke(UpdateProgress(50));
this.Dispatcher.BeginInvoke(bigControlBuilder.Build(50,75));
this.Dispatcher.BeginInvoke(UpdateProgress(75));
this.Dispatcher.BeginInvoke(bigControlBuilder.Build(75,100));
this.Dispatcher.BeginInvoke(UpdateProgress(100));
this.Dispatcher.BeginInvoke(this.Children.Add(bigControlBuilder.GetControl()));
Update:
To make complex control more responsive you could also try UI-Virtualization/Data-Virtualisation:
Only load and show those visual items of the data items that are currently visible to ther user. Do not load and show visual items that are scrolled offscreen are to small to see or are in any other way invisible to the user. Upon userinteraction unload items that become invisble, load items that become visible.
To answer your question, I suppose it is possible to set _dispatcher using reflection but I would not recommend it at all. There is a deeply ingrained notion in WPF of thread affinity and STA so I wouldn't mess with that.
bitbonk's approach is a good one.
Another approach we have used in a project of ours was to create a second UI thread and have a progress indicator be rendered by the second UI thread while the first UI thread is building the UI. As long as the progress bar stays in the visual tree owned by the second UI thread, you should be good.

How can you "plug into" the WPF rendercycle to get accurate results from VisualTreeHelper?

I am currently writing a WPF User control (PARENT) which can contain multiple child (CHILD) user controls. I am connecting the (CHILD) controls with polylines.
To do this I need the locations of the CHILD user controls.
However, the locations i get from VisualTreeHelper.GetOffset are zero. WPF is probably not finished yet with sizing and placing the CHILD controls in memory, because when I put my code in the OnLoaded event handler of my control I do get correct locations for my child controls.
This is a problem because even after I loaded my PARENT control I still want to be able to add CHILD controls and update my lines.
How I have currently solved this is by overriding the OnRender of my CHILD controls and raising an event for my MAIN control so it knows it can use the VisualTreeHelper to obtain correct values.
This however means that I will need to redraw my lines untill the last CHILD control has finished rendering.
My current solution feels more like a workaround than a solution. How can you "plug into" the WPF rendercycle to get accurate results from VisualTreeHelper?
Try overriding ArrangeOverride or OnRender on the parent control. WPF does a three-stage layout/render pass: first, it asks the controls how much space they need (measure), then it tells the controls how much space they actually have given all of the requests received (arrange), and then it actually draws everything inside the regions each control was given (render). For this to work, it follows the visual tree - the root element (say, Window) is told to measure; it has to ask its children to measure, who have to ask their children, etc. all the way down to the leaf elements before the whole thing starts returning all the way back to the root. Then it starts the arrange pass, and finally the render pass.
By watching the children's OnRender, you're implicitly saying that you want to start drawing after measure and arrange have occurred, but before the parent starts drawing itself. This can be made equivalent to the parent's OnRender - which wraps the children's OnRenders. You might also be able to do this in the parent's ArrangeOverride, which wraps the children's arrange pass, which is where (IIRC) the values VisualTreeHelper.GetOffset() accesses actually get set.
Finally, by looking at the parent control, you're implicitly waiting for all the child controls to finish arranging/rendering - which means that you won't have to monkey around trying to figure out when the last child has finished rendering.

WPF - CAL - Multiple parents for single instance of control?

I am working on a PRISM / CAL solution, but the problem may be WPF specific:
If I create one instance of an control (e.g. TextBlock) and add it as child to a StackPanel, there is no way to add it as "child" to another StackPanel (parent already set error). I kind of understand the reason (it also occurs when using the RegionManager).
But what is the suggested way if a visual control is very complex and should be created only one time and used in two places? I agree that is does not really make sense to show an identical control 2 times on the screen, but there might be cases where it is useful (e.g. an "Close All" Button).
I know that in the button case, I should just create two buttons both databound to one ICommand. But does this rule also apply with much more complex controls (always create new instances)...
I stumbled on this problem when creating a layout switcher, which creates the button list and the stack panel for each GUI seperately, but uses a static ObservableCollection of buttons as source (which causes strange bugs)..
Any ideas on this topic?
Chris
This is normally handled by templates. That is, you abstract out the data into a particular type, and associate a template with that type. Then you place the instance of that data any number of times into your visual tree and have WPF render it with the template.
In short, don't add a TextBlock to your StackPanel. Instead, add an instance of your data type (eg. Customer) and associate a DataTemplate with the Customer type. There is no way to parent the same UIElement in multiple places.
You can add your control (or collection of controls) as a resource and refer to them via binding in your controls. This will implicitly create a copy (they will be Freezable and WPF will copy them).
Generally you should be using DataTemplates as Kent suggests, but if you have a special case, this will likely work.

Resources