views:

263

answers:

7

Hi

In WPF, I want to create a Window that looks like the following:

Application with user controls

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

+1  A: 

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>
Joseph
Yes, that would be great. I edited the question to note that #2 could be used in another place.
PBelanger
@PBelanger that's fine, but that just means that you're user controls should be further encapsulated so that you can change their respective layouts. Having the bottom part of Area2 be its own control allows you to place it where ever you want on any number of layout schemes.
Joseph
+1  A: 

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.

Jean-Sébastien Desfossés
+1  A: 

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.

esylvestre
A: 

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

Jobi Joy
If he asked the question it's probably because he needs to use the UserControl alone. The question is: "how to automatically move/resize UserControl/Panel at runtime depending on the context?", NOT how to make it another way.
Cédric Boivin
Yeah, but the suggestion is still better than nothing...
esylvestre
I dont know why you down vote this? The solution is exactly similar to what @Joseph added XAML for. Mine is just visual representation on the same
Jobi Joy
+1  A: 

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:

  1. In ArrangeOverride, schedule a Dispatcher.BeginInvoke callback to do the actual arranging and report full size used
  2. In the callback, get rectangles representing all my siblings' bounding boxes in my parent's coordinate space.
  3. Sort Y coordinates (top and bottom) of all rectangles placed
  4. 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
  5. Monitor siblings' VisualTransform for changes, if any call InvalidateArrange()
Ray Burns
A: 

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>
Nir
A: 

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

Martin Lamontagne