11

Hopefully no one else has already asked this question, but I have searched and cannot find any mention. Feel free to point me in the right direction if I missed another question that explains this.

I have a WrapPanel with data-bound items that essentially contain an icon and some variable length text (this is a legend for a chart). I really like the fact that the items appear in neat columns when I set the ItemWidth to some set value. However, due to the high variability in length of the text in each item, I cannot easily choose a specific value that will work for all cases. That is, in some cases the text for all the items might be short, so a smaller value for ItemWidth would be appropriate. But other times, such a small ItemWidth would result in truncated text among some of the items.

I figure I can data bind ItemWidth to the WrapPanel's children somehow in order to extract the width of each item (and find the maximum width, and use that as the ItemWidth, etc), but I am leery of doing so due to the potential of data binding to the wrong thing. As in, binding to something that changes when ItemWidth changes, leading to infinite loops (or at least, loops that repeat more times than necessary.

What is the best way to set this so that the ItemWidth is only as large as it needs to be to prevent truncation?

EDIT:

I want to retain the feature offered by the WrapPanel that allows there to be a variable number of columns of items, depending on the space allowed for the WrapPanel itself.

skybluecodeflier
  • 1,339
  • 13
  • 26
  • Duplicate question? http://stackoverflow.com/questions/2817620/wpf-wrappanel-all-items-should-have-the-same-width – J.H. May 07 '14 at 17:41
  • @J.H.: I want the feature that the number of columns is variable, not fixed. That feature is easy in WrapPanel, hard in Grid (as far as I know). – skybluecodeflier May 07 '14 at 17:44
  • Maybe I'm missing something but... yes, that is how it works. I'll post an answer with xaml markup so you can try it for yourself. Your wrap panel children consists of one cell grids. Your real content goes in the individual one cell grids. – J.H. May 07 '14 at 18:05
  • Why? I would think a wrapping textblock would with a width as big as the largest chart would be a nicer presentation. – paparazzo May 07 '14 at 18:07
  • @J.H. I see what you mean now. That makes sense, and looks like it would work. Though it isn't important for my solution, is there a lighter object I could use as the wrapper than a grid? – skybluecodeflier May 07 '14 at 18:20
  • @Blam: I'm not visualizing what you are describing. Explain more. – skybluecodeflier May 07 '14 at 18:20
  • 1
    Not that I am aware of - has to be grids. You might be able to get something like MaxOvrdrv was talking about working -- loop through the children, get the max size, apply it the wrap panel ItemWidth. I'd rather stick with the grids though, unless performance is an issue. – J.H. May 07 '14 at 18:27
  • @J.H.: Ok, thanks much for your help. Now to test this stuff out... – skybluecodeflier May 07 '14 at 18:32
  • 1
    Wrap the text to more than one line. – paparazzo May 07 '14 at 19:32
  • @Blam: Actually that was a good suggestion. Though it doesn't work in some of my cases because of how few words there are in some of my legend items (long words mixed with short ones, but not many for each legend item). It also wouldn't look the best in my case anyways. You would have to see it. I may implement that for some situations, though... – skybluecodeflier May 08 '14 at 01:42

3 Answers3

16

You could wrap each item in a Grid, and use the Grid's ShareSizeScope property to make sure all items share the same width.

For example, on your WrapPanel you would set Grid.IsSharedSizeScope to true

<WrapPanel Grid.IsSharedSizeScope="True">

Then you'd wrap each item in a single cell Grid that uses the SharedSizeGroup property to tell them that they all share a size

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition SharedSizeGroup="SharedGroup" />
    </Grid.ColumnDefinitions>
    <ContentPresenter />
</Grid>

If you want an example, a quick Google search gave me this answer which contains a code example.

I'd recommend performance testing this too if you have a large set of data.

Community
  • 1
  • 1
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • Is there another option that would be more performant than using a whole Grid in each element? – skybluecodeflier May 07 '14 at 18:27
  • @skybluecodeflier I can't think of any easy ones. I'm sure you could do a converter and measure each string and return the size of the largest one, but that seems like it'd be an even worse idea. You could probably create your own panel too that arranges items like this, but I don't have much experience with creating custom panels so couldn't tell you for sure. This option may actually have good performance though. I've never tested it with a larger data source so it would be a good idea to double-check. – Rachel May 07 '14 at 18:38
2

Try the following. The part to pay attention to is the Grid.IsSharedSizeScope="True" on the wrap panel AND the column definition's SharedSizeGroup property.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <WrapPanel Grid.IsSharedSizeScope="True">
            <WrapPanel.Resources>
                <Style TargetType="TextBlock">
                    <Setter Property="Padding" Value="2" />
                    <Setter Property="HorizontalAlignment" Value="Center" />
                </Style>
            </WrapPanel.Resources>
            <WrapPanel.Children>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="group1" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="abcdefghijklmnopqrstuvwxyz" />
                </Grid>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="group1" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="1234567890" />
                </Grid>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="group1" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="abcdefghijklmnopqrstuvwxyz" />
                </Grid>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="group1" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="1234567890" />
                </Grid>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="group1" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="abcdefghijklmnopqrstuvwxyz" />
                </Grid>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="group1" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="1234567890" />
                </Grid>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="group1" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="abcdefghijklmnopqrstuvwxyz" />
                </Grid>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="group1" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="1234567890" />
                </Grid>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="group1" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="abcdefghijklmnopqrstuvwxyz" />
                </Grid>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="group1" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="1234567890" />
                </Grid>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="group1" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="abcdefghijklmnopqrstuvwxyz" />
                </Grid>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="group1" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="1234567890" />
                </Grid>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="group1" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="abcdefghijklmnopqrstuvwxyz" />
                </Grid>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="group1" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="1234567890" />
                </Grid>
            </WrapPanel.Children>
        </WrapPanel>
    </Grid>
</Window>
J.H.
  • 4,232
  • 1
  • 18
  • 16
0

try setting the itemwidth property to Auto... see here:

http://msdn.microsoft.com/en-us/library/system.windows.controls.wrappanel.itemwidth(v=vs.110).aspx

to perform this "width set" programmatically, you could do the following, once the control has been rendered.

protected override void OnRender(DrawingContext dc)
{

int largest = 0;
foreach(UIElement child in this.myWrapPanel.Children)
{
    if(child.Width>largest)
        largest = child.Width;
}

this.myWrapPanel.ItemWidth = largest;


}
MaxOvrdrv
  • 1,780
  • 17
  • 32
  • Setting to Auto removes the "nice neat columns" I mentioned explicitly in my question. Not a solution. – skybluecodeflier May 07 '14 at 17:51
  • In that case you are looking for code to loop through each column, get its width, compare to see if it's the largest, and when your loop is done and you have the largest one, set your wrap panels item width to the largest one... there is no other option than the one i gave you above except for a programmatic one. If you want to know how to do that i can post that? – MaxOvrdrv May 07 '14 at 17:54
  • @MaxOrdrv: Huh? WrapPanels don't have defined columns. The columns appear implicitly due to how ItemWidth is used and how the wrapping occurs. If I used a Grid, I could imagine it being a lot of work to dynamically choose how many columns I wanted, etc. – skybluecodeflier May 07 '14 at 17:57
  • What OnRender are you overriding? This would not work so well if I had to use the WrapPanel as the ItemsPanel for another object, or if I had to use the WrapPanel in multiple places. The other solutions appear more elegant. – skybluecodeflier May 07 '14 at 18:22
  • i guess i missed the part where you are working within a grid... i was solely working on the WrapPanel, and i was not "extending" it either... which by the way, WrapPanel is indeed a self-contained item, with methods, properties, etc... http://msdn.microsoft.com/en-us/library/system.windows.controls.wrappanel.aspx ... i used that, not the grid. Up to you anyway. f.y.i. though: I just tried my solution locally and it works marvelously. Happy Coding sir! – MaxOvrdrv May 07 '14 at 18:30