0
votes

first time post for me :)

I'm having this issue with the width of my UserControl that i just can't seem to figure out.

Essentially i have a UserControl that contains an ItemsControl (panle is a horizontal StackPanel). The item's DataTemplate is a standard Button. I bind a List of strings to the ItemsControl, and each string gets bound to the Content property of the Button. Works fine till now.

What i need to do is to keep track of the width of each single item (Button with string inside) in the ItemsControl even if they are not rendered. I need to do this to dynamically (while resizing) remove items that exceed the maximum width of the UserControl, and add them back again if there is enough space.

To achieve this i measure the width of each string using the following function:

    private double GetTextWidth(string text)
    {
        FormattedText ft = new FormattedText(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch), this.FontSize, this.Foreground);
        ft.Trimming = TextTrimming.CharacterEllipsis;
        return ft.WidthIncludingTrailingWhitespace;
    }

I then manually add a margin of 6 to the returned value, this margin is the spacing needed by the Button. Summing all the generated values from the strings in my List i should get almost the same width as my UserControl. Since all layouts are stretched (even the UserControl in the Window is), there is no margin or padding going on (as far as i can see) and there is only a single Border which has a width of 1.

Problem is that this doesn't work, it requires me to manually add a pretty large value (45-65 units) to the calculated width to get it to work "pixel-perfect".

I obviously tried to find the root of this problem, but was unable. First i thought it's caused by a DIP <-> Pixel problem, but that doesn't seem the case. I measured the width of a single string and then the width of the button containing the same string. In all scenarios the first and the second differ by 6 units. There is no visible space between the buttons so i really can't explain where so much overhead is coming from. The only thing i noticed is that the value i need to add changes if i change the FontSize, but that's pretty obvious...

Probably i'm missing something big, any ideas? Thanks for reading!

1

1 Answers

0
votes

If I understand you correctly, you want to hide items that are not completely visible on the screen.

I've done this in the past with a helper method that will tell me if a control is fully visible or not, and that sets the control's visibility to Hidden if the control is not fully visible. I implemented it in the Loaded and SizeChanged events.

Here's the helper class which returned the rendered control's visibility:

public enum ControlVisibility
{
    Hidden,
    Partial,
    Full,
    FullHeightPartialWidth,
    FullWidthPartialHeight
}


/// <summary>
/// Checks to see if an object is rendered visible within a parent container
/// </summary>
/// <param name="child">UI element of child object</param>
/// <param name="parent">UI Element of parent object</param>
/// <returns>ControlVisibility Enum</returns>
public static ControlVisibility IsObjectVisibleInContainer(
    FrameworkElement child, UIElement parent)
{
    GeneralTransform childTransform = child.TransformToAncestor(parent);
    Rect childSize = childTransform.TransformBounds(
        new Rect(new Point(0, 0), new Point(child.Width, child.Height)));

    Rect result = Rect.Intersect(
        new Rect(new Point(0, 0), parent.RenderSize), childSize);

    if (result == Rect.Empty)
    {
        return ControlVisibility.Hidden;
    }
    if (result.Height == childSize.Height && result.Width == childSize.Width)
    {
        return ControlVisibility.Full;
    }
    if (result.Height == childSize.Height)
    {
        return ControlVisibility.FullHeightPartialWidth;
    }
    if (result.Width == childSize.Width)
    {
        return ControlVisibility.FullWidthPartialHeight;
    }
    return ControlVisibility.Partial;
}

The code-behind the Loaded and SizeChanged events looked something like this:

ControlVisibility ctrlVisibility= 
    WPFHelpers.IsObjectVisibleInContainer(button, parent);

if (ctrlVisibility == ControlVisibility.Full 
    || isVisible == ControlVisibility.FullWidthPartialHeight)
{
    button.Visibility = Visibility.Visible;
}
else
{
    button.Visibility = Visibility.Hidden;
}