Ширина расширенного пользовательского элемента управления WPF не соответствует ширине дочерней горизонтальной стековой панели

#wpf #user-controls #measurement

#wpf #пользовательские элементы управления #измерение

Вопрос:

первый раз для меня 🙂

У меня возникла проблема с шириной моего пользовательского элемента управления, которую я просто не могу понять.

По сути, у меня есть пользовательский элемент управления, который содержит ItemsControl (панель — это горизонтальная панель стека). DataTemplate элемента является стандартной кнопкой. Я привязываю список строк к ItemsControl, и каждая строка привязывается к свойству содержимого кнопки. До сих пор работает нормально.

Что мне нужно сделать, так это отслеживать ширину каждого отдельного элемента (кнопки со строкой внутри) в ItemsControl, даже если они не отображаются. Мне нужно сделать это, чтобы динамически (при изменении размера) удалять элементы, которые превышают максимальную ширину пользовательского элемента управления, и добавлять их обратно, если достаточно места.

Для достижения этого я измеряю ширину каждой строки, используя следующую функцию:

     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;
    }
  

Затем я вручную добавляю к возвращаемому значению запас в 6, этот запас представляет собой интервал, необходимый для кнопки.
Суммируя все сгенерированные значения из строк в моем списке, я должен получить почти ту же ширину, что и мой пользовательский элемент управления. Поскольку все макеты растянуты (даже пользовательский элемент управления в окне), поля или отступы не выполняются (насколько я вижу), и есть только одна граница шириной 1.

Проблема в том, что это не работает, мне требуется вручную добавить довольно большое значение (45-65 единиц) к вычисляемой ширине, чтобы заставить ее работать «идеально в пикселях».

Я, очевидно, пытался найти корень этой проблемы, но не смог. Сначала я подумал, что это вызвано проблемой DIP <-> Pixel, но, похоже, это не так. Я измерил ширину одной строки, а затем ширину кнопки, содержащей ту же строку. Во всех сценариях первый и второй отличаются на 6 единиц. Между кнопками нет видимого пространства, поэтому я действительно не могу объяснить, откуда берется столько накладных расходов. Единственное, что я заметил, это то, что значение, которое мне нужно добавить, изменяется, если я меняю размер шрифта, но это довольно очевидно…

Возможно, я упускаю что-то важное, есть идеи? Спасибо за чтение!

Ответ №1:

Если я вас правильно понял, вы хотите скрыть элементы, которые не полностью видны на экране.

Я делал это в прошлом с помощью вспомогательного метода, который сообщит мне, полностью ли виден элемент управления или нет, и который устанавливает видимость элемента управления на Hidden , если элемент управления не полностью виден. Я реализовал это в Loaded и SizeChanged событиях.

Вот вспомогательный класс, который вернул видимость отображаемого элемента управления:

 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 amp;amp; 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;
}
  

Код, лежащий в основе событий Loaded и SizeChanged , выглядел примерно так:

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

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

Комментарии:

1. Спасибо, Рейчел, ваш код выглядит интересным. Проблема в том, что я не могу получить прямой доступ к дочерним элементам (поскольку они шаблонные), поэтому любой доступ к ItemsControl просто возвращает заданный объект string, а не кнопку, которая мне понадобится для определения ее отображаемого размера.

2. Я бы поставил это за Loaded / SizeChanged событиями Button внутри вашего Template . Чтобы получить родительский элемент, вы должны иметь возможность ссылаться на ItemsControl по имени. Если это не работает, вы можете использовать вспомогательные классы, найденные по следующей ссылке, чтобы перейти вверх по визуальному дереву и найти StackPanel содержащие кнопки. rachel53461.wordpress.com/2011/10/09 /…

3. Работает как шарм, даже если я не пошел точно так, как вы описали, ваш ответ мне очень помог. Большое вам спасибо!