WPF Layout of user control as a run
In WPF, I want to create a Window that looks like the following开发者_如何学C:
Application with user controls http://www.freeimagehosting.net/uploads/86209e1a87.png
On the screen are four user control, #1, 2, 3, 4. As you can see, user control 2 should not be rendered as a box, but inlined.
If this were a WPF flow document:
- 1, 3, 4 would be a paragraph (boxing)
- 2 a run (inlining)
The reason is that 2 could be used in another form without splitting for 3.
Do you have any idea how to do this in a proper manner ?
Some idea already thought of:
- 2 is and ordinary user control (boxing). When placed in the window, 2, 3, 4 are placed in a canvas, using there Z-Order and margin ton control how they are rendered
- 2 has a grid already formatted so that it could accept 3 and 4 in it as ContentControl, and we inject them via Xaml or code behind
- 2 exposes the main Grid as a property, and via the Attached Property goodness, we add the data for 3 and 4
- We create our own layout control and implement the Arrange and Measure methods to create a layout acting like a Run
And some others that are not as clean...
Any thoughts ?
Thanks,
Patrick
I think you have your form broken up incorrectly, IMO.
Area 2 should not include the bottom piece you're describing that you want to have "inlined".
It should separate (as far as layout is concerned), and probably fit in a grid with 2 columns and 2 rows along with Area 3 and Area 4. Area 3 would then need to have a rowspan of 2.
The XAML, in this case, would actually be pretty clean if done this way.
It might look something like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<uC:Area1 Grid.Row="0"><!-- Area Control here --></uC:Area1>
<uC:Area2 Grid.Row="1"><!-- Area Control here --></uC:Area2>
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<uC:AreaDuree Grid.Row="0" Grid.Column="0">
<!-- Area Control here -->
</uC:AreaDuree>
<uC:Area4 Grid.Row="1" Grid.Column="0">
<!-- Area Control here -->
</uC:Area4>
<uC:Area3 Grid.RowSpan="2" Grid.Column="1">
<!-- Area Control here -->
</uC:Area3>
</Grid>
</Grid>
I suggest you to split your Control #2 in 3 or 4 more specific UserControl. Then load #2, #3, #4 inside a WrapPanel. Check this link: Layout of UserControl to implement an intelligent wrap panel Arrange and Mesure strategy.
For me, to simplify the life of your designer, I would suggest you the solution of ContentControl.
This way, your designer could easily add their controls and for you, just sure to make your Usercontrol #2 smart enough to render your layour the right way.
I'm surprised nobody has yet suggested using an actual FlowDocument to lay out your UI.
I've used FlowDocument
before to achieve complex arrangements. It works quite well except that some property inheritance is blocked (TextElement.FontSize
, for example). This is not difficult to work around.
In your case, just break #2 into a separate UserControl per section, and inside your FlowDocument, use InlineUIContainer to wrap each section.
<Window>
<DockPanel>
... toolbars, etc ...
<FlowDocumentScrollViewer ...>
<FlowDocument>
...
<InlineUIContainer>
<my:PartialNumberTwo DataContext="{Binding ...}" />
</InlineUIContainer>
...
Of course there are limits to what FlowDocument can do, so it may not work for your scenario.
An extremely flexible option is to create a custom panel that lays out its children in a WrapPanel style, but does by fitting rectangles, and before it starts it marks out any area overlapped by the panel's own siblings as unavailable.
Such a panel would be used like this:
<Grid ...>
... RowDefinitions, ColumnDefinitions ...
<panels:WrapIntoRemainingSpacePanel RowSpan="4" ColumnSpan="3"> <!-- fill whole grid -->
<my:Control2Part1 />
<my:Control2Part2 />
<my:Control2Part3 />
<my:Control2Part4 />
</panels:WrapIntoRemainingSpacePanel>
<my:Control1 Grid.Row="0" Grid.ColumnSpan="3" />
<my:Control3 Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="2" />
<my:Control4 Grid.Row="3" Grid.RowSpan="2" />
This panel would be implemented as follows:
- In ArrangeOverride, schedule a Dispatcher.BeginInvoke callback to do the actual arranging and report full size used
- In the callback, get rectangles representing all my siblings' bounding boxes in my parent's coordinate space.
- Sort Y coordinates (top and bottom) of all rectangles placed
- Place each child by finding the first Y coordinate in the sorted list at which there is horizontal space somewhere for the contents, and putting it as far left as possible
- Monitor siblings' VisualTransform for changes, if any call InvalidateArrange()
I think it's a simple grid, the "trick" is that it's perfectly fine to have one control cover another - WPF will always render them in the same order:
I used Height="Auto" for all grid rows, depending on how you user controls are set up you may need to specify a real height in order to get them to line up correctly.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<uc:UserControl1 Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"/>
<uc:UserControl2 Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Grid.RowSpan="2"/>
<uc:UserControl3 Grid.Row="2" Grid.Column="1" Grid.RowSpan="2"/>
<uc:UserControl4 Grid.Row="3" Grid.Column="0"/>
<Grid>
It's 1 big Grid, with 3 rows and 2 columns. Each zone of that grid contains a grid able to receive your users controls. So the grid number3 just need to get it's top margin set to -75. It will overlaps the grid number 2 if it's put lower it in the xaml. Then you just need to lock your columns and row depending how you want it to react.
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.55*"/>
<ColumnDefinition Width="0.45*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.363*"/>
<RowDefinition Height="0.369*"/>
<RowDefinition Height="0.268*"/>
</Grid.RowDefinitions>
<Grid Grid.ColumnSpan="2" Background="#FF48C5D0"/>
<Grid Margin="1,0,0,0" Grid.ColumnSpan="2" Grid.Row="1" Background="#FFD2A268"/>
<Grid Margin="1,0,0,0" Grid.Row="2" Background="#FFB7F075"/>
<Grid Grid.Column="1" Grid.Row="2" Background="#FFB129EC" Margin="0,-75,0,0"/>
</Grid>
Martin Lamontagne
May be think about having #3 and #4 as part of user control #2 then things makes simpler to layout. A top level grid lays out #1 and #2
See the Second user control layout. And it all grid with * size so that it gets resize according to the window size.
alt text http://img5.imageshack.us/img5/4419/40506576.jpg
精彩评论