Подтвердить что ты не робот

Как я могу сделать элементы, расположенные в горизонтальной папке StackPanel, общей базой для их текстового контента?

Вот тривиальный пример проблемы, с которой я сталкиваюсь:

<StackPanel Orientation="Horizontal">
    <Label>Foo</Label>
    <TextBox>Bar</TextBox>
    <ComboBox>
        <TextBlock>Baz</TextBlock>
        <TextBlock>Bat</TextBlock>
    </ComboBox>
    <TextBlock>Plugh</TextBlock>
    <TextBlock VerticalAlignment="Bottom">XYZZY</TextBlock>
</StackPanel>

Каждый из этих элементов, кроме TextBox и ComboBox, вертикально позиционирует текст, который они содержат по-разному, и выглядит просто уродливым.

Я могу выровнять текст в этих элементах, указав a Margin для каждого. Это работает, за исключением того, что маржа находится в пикселях, а не относительно разрешения экрана или размера шрифта или любой другой вещи, которая будет переменной.

Я даже не уверен, как я вычислил правильное нижнее поле для элемента управления во время выполнения.

Какой лучший способ сделать это?

4b9b3361

Ответ 1

Проблема

Итак, я понимаю, что проблема заключается в том, что вы хотите вывести элементы управления горизонтально в StackPanel и выровнять их сверху, но текст в каждой строке управления вверх. Кроме того, вы не хотите устанавливать что-либо для каждого элемента управления: либо Style, либо Margin.

Основной подход

Корень проблемы состоит в том, что разные элементы управления имеют разное количество "накладных расходов" между границей элемента управления и текстом внутри. Когда эти элементы управления выровнены вверху, текст внутри отображается в разных местах.

Итак, мы хотим применить вертикальное смещение, настроенное для каждого элемента управления. Это должно работать для всех размеров шрифтов и всех DPI: WPF работает в независимых от устройства измерениях длины.

Автоматизация процесса

Теперь мы можем применить Margin для получения нашего смещения, но это означает, что мы должны поддерживать это на каждом элементе управления в StackPanel.

Как мы автоматизируем это? К сожалению, было бы очень сложно получить пуленепробиваемый раствор; можно переопределить шаблон управления, который изменит количество служебных данных компоновки в элементе управления. Но возможно приготовить элемент управления, который может сэкономить много ручной работы по выравниванию, если мы можем связать тип управления (TextBox, Label и т.д.) С заданным смещением.

Решение

Существует несколько разных подходов, но я думаю, что это проблема макета и нуждается в некоторой пользовательской логике измерения и упорядочивания:

public class AlignStackPanel : StackPanel
{
    public bool AlignTop { get; set; }

    protected override Size MeasureOverride(Size constraint)
    {
        Size stackDesiredSize = new Size();

        UIElementCollection children = InternalChildren;
        Size layoutSlotSize = constraint;
        bool fHorizontal = (Orientation == Orientation.Horizontal);

        if (fHorizontal)
        {
            layoutSlotSize.Width = Double.PositiveInfinity;
        }
        else
        {
            layoutSlotSize.Height = Double.PositiveInfinity;
        }

        for (int i = 0, count = children.Count; i < count; ++i)
        {
            // Get next child.
            UIElement child = children[i];

            if (child == null) { continue; }

            // Accumulate child size.
            if (fHorizontal)
            {
                // Find the offset needed to line up the text and give the child a little less room.
                double offset = GetStackElementOffset(child);
                child.Measure(new Size(Double.PositiveInfinity, constraint.Height - offset));
                Size childDesiredSize = child.DesiredSize;

                stackDesiredSize.Width += childDesiredSize.Width;
                stackDesiredSize.Height = Math.Max(stackDesiredSize.Height, childDesiredSize.Height + GetStackElementOffset(child));
            }
            else
            {
                child.Measure(layoutSlotSize);
                Size childDesiredSize = child.DesiredSize;

                stackDesiredSize.Width = Math.Max(stackDesiredSize.Width, childDesiredSize.Width);
                stackDesiredSize.Height += childDesiredSize.Height;
            }
        }

        return stackDesiredSize; 
    }

    protected override Size ArrangeOverride(Size arrangeSize)
    {
        UIElementCollection children = this.Children;
        bool fHorizontal = (Orientation == Orientation.Horizontal);
        Rect rcChild = new Rect(arrangeSize);
        double previousChildSize = 0.0;

        for (int i = 0, count = children.Count; i < count; ++i)
        {
            UIElement child = children[i];

            if (child == null) { continue; }

            if (fHorizontal)
            {
                double offset = GetStackElementOffset(child);

                if (this.AlignTop)
                {
                    rcChild.Y = offset;
                }

                rcChild.X += previousChildSize;
                previousChildSize = child.DesiredSize.Width;
                rcChild.Width = previousChildSize;
                rcChild.Height = Math.Max(arrangeSize.Height - offset, child.DesiredSize.Height);
            }
            else
            {
                rcChild.Y += previousChildSize;
                previousChildSize = child.DesiredSize.Height;
                rcChild.Height = previousChildSize;
                rcChild.Width = Math.Max(arrangeSize.Width, child.DesiredSize.Width);
            }

            child.Arrange(rcChild);
        }

        return arrangeSize;
    }

    private static double GetStackElementOffset(UIElement stackElement)
    {
        if (stackElement is TextBlock)
        {
            return 5;
        }

        if (stackElement is Label)
        {
            return 0;
        }

        if (stackElement is TextBox)
        {
            return 2;
        }

        if (stackElement is ComboBox)
        {
            return 2;
        }

        return 0;
    }
}

Я начал с методов StackPanel Measure и Arrange, затем удалил ссылки на прокрутки и события ETW и добавил буфер интервала, необходимый в зависимости от типа присутствующего элемента. Логика влияет только на горизонтальные панели стека.

Свойство AlignTop определяет, будет ли интервал выравнивания текста сверху или снизу.

Номера, необходимые для выравнивания текста, могут измениться, если элементы управления получают собственный шаблон, но вам не нужно помещать разные теги Margin или Style для каждого элемента коллекции. Другим преимуществом является то, что теперь вы можете указать Margin на дочерние элементы управления, не мешая выравниванию.

Результаты:

<local:AlignStackPanel Orientation="Horizontal" AlignTop="True" >
    <Label>Foo</Label>
    <TextBox>Bar</TextBox>
    <ComboBox SelectedIndex="0">
        <TextBlock>Baz</TextBlock>
        <TextBlock>Bat</TextBlock>
    </ComboBox>
    <TextBlock>Plugh</TextBlock>
</local:AlignStackPanel>

align top example

AlignTop="False":

align bottom example

Ответ 2

Это работает, за исключением того, что маржа находится в пикселях, а не относительно разрешения экрана или размера шрифта или любой другой вещи, которая будет переменной.

Ваши предположения неверны. (Я знаю, потому что у меня были те же предположения и те же проблемы).

На самом деле пиксели

Прежде всего, маржа не находится в пикселях. (Вы уже думаете, что я сумасшедший, не так ли?) Из документов для FrameworkElement.Margin:

Единица измерения по умолчанию для измерения толщины не зависит от устройства (1/96-й дюйм).

Я думаю, что предыдущие версии документации, как правило, называли это "пиксель" или, позднее, "независимый от устройства пиксель". Со временем они поняли, что эта терминология была огромной ошибкой, потому что WPF на самом деле ничего не делает с точки зрения физических пикселей - они использовали этот термин для обозначения чего-то нового, но их аудитория предполагала, что это означает, что это всегда было. Итак, теперь документы стремятся избежать путаницы, уклоняясь от любой ссылки на "пиксели"; теперь они используют "не зависящий от устройства блок".

Если настройки экрана вашего компьютера установлены на 96 точек на дюйм (настройка по умолчанию для Windows), то эти независимые от устройства устройства будут соответствовать друг другу с пикселями. Но если вы установили свои настройки дисплея на 120dpi (называемые "большими шрифтами" в предыдущих версиях Windows), ваш элемент WPF с высотой = "96" будет на самом деле иметь максимум 120 физических пикселей.

Таким образом, ваше предположение о том, что поле "не будет" относительно разрешения дисплея, неверно. Вы можете проверить это самостоятельно, написав свое приложение WPF, затем переключившись на 120dpi или 144dpi и запустив приложение, и заметив, что все по-прежнему выстраивается в линию. Ваша озабоченность по поводу того, что маржа "не относится к разрешению экрана", не является проблемой.

(В Windows Vista вы переключаетесь на 120dpi, щелкнув правой кнопкой мыши на рабочем столе > Персонализация и нажав ссылку "Настроить размер шрифта (DPI)" на боковой панели. Я считаю, что это похоже на Windows 7. Остерегайтесь того, что это требует перезагрузка при каждом его изменении.)

Размер шрифта не имеет значения

Что касается размера шрифта, это также не проблема. Вот как вы можете это доказать. Вставьте следующий XAML в Kaxaml или любой другой редактор WPF:

<StackPanel Orientation="Horizontal" VerticalAlignment="Top">  
  <ComboBox SelectedIndex="0">
    <TextBlock Background="Blue">Foo</TextBlock>
  </ComboBox>
  <ComboBox SelectedIndex="0" FontSize="100pt">
    <TextBlock Background="Blue">Foo</TextBlock>
  </ComboBox>
</StackPanel>

Combobox font size does not affect margins

Обратите внимание, что размер шрифта ComboBox не зависит от размера шрифта. Расстояние от вершины ComboBox до вершины TextBlock точно такое же, независимо от того, используете ли вы размер шрифта по умолчанию или полностью экстремальный размер шрифта. Встроенный маркер combobox является постоянным.

Не имеет значения, если вы используете разные шрифты, если вы используете один и тот же шрифт как для ярлыка, так и для содержимого ComboBox, а также для того же размера шрифта, стиля шрифта и т.д. Вершины этикеток будут линии вверх, и если вершины выстраиваются в линию, базовые линии тоже будут.

Итак, да, используйте поля

Я знаю, это звучит неряшливо. Но WPF не имеет встроенного выравнивания базовой линии, а маржа - это механизм, который они нам дали для решения этой проблемы. И они сделали так, чтобы поля были полезными.

Вот совет. Когда я впервые тестировал это, я не был уверен, что combobox chrome будет точно соответствовать 3-пиксельному верхнему краю. В конце концов, многие вещи в WPF, включая и особенно размеры шрифтов, измеряются в точном, нецелом размеры и затем привязаны к пикселям устройства - как я мог знать, что вещи не будут смещены при настройках экрана 120dpi или 144dpi из-за округления?

Ответ оказывается легким: вы вставляете макет вашего кода в Kaxaml, а затем увеличиваете масштаб (в нижней левой части окна находится панель слайдера масштабирования). Если все по-прежнему выстраивается в линию, даже когда вы увеличены, тогда вы в порядке.

Вставьте следующий код в Kaxaml, а затем начните масштабирование, чтобы убедиться в том, что поля действительно являются способом перехода. Если красное оверлейное изображение совпадает с верхней частью синих надписей со 100-кратным увеличением, а также с 125-кратным увеличением (120 точек на дюйм) и 150-процентным увеличением (144dpi), вы можете быть уверены, что он будет работать с чем угодно. Я пробовал это, и в случае с ComboBox я могу сказать вам, что они использовали интегральный размер для хрома. Верхний край 3 будет получать ваш ярлык каждый раз с текстом ComboBox.

(Если вы не хотите использовать Kaxaml, вы можете просто добавить временный ScaleTransform в свой XAML, чтобы масштабировать его до 1,25 или 1,5, и убедитесь, что все еще выстроено в линию. Это будет работать, даже если ваш предпочтительный редактор XAML У меня есть функция масштабирования.)

<Grid>
  <StackPanel Orientation="Horizontal" VerticalAlignment="Top">  
    <TextBlock Background="Blue" VerticalAlignment="Top" Margin="0 3 0 0">Label:</TextBlock>
    <ComboBox SelectedIndex="0">
      <TextBlock Background="Blue">Combobox</TextBlock>
    </ComboBox>
  </StackPanel>
  <Rectangle Fill="#6F00" Height="3" VerticalAlignment="Top"/>
</Grid>
  • При 100%: Label + combobox + margin, 100 percent
  • При 125%: Label + combobox + margin, 125 percent
  • В 150%: Label + combobox + margin, 150 percent

Они всегда выстраиваются в линию. Поля - путь.

Ответ 3

VerticalContentAlignment и HorizontalContentAlignment, затем укажите заполнение и поле 0 для каждого дочернего элемента управления.

Ответ 4

У каждого UIElement есть встроенное дополнение, которое отличается для метки, текстового блока и любого другого элемента управления. Я думаю, что настройка дополнения для каждого элемента управления сделает для вас. **

Маржа определяет пространство относительно другое UIElement в пикселях, которое может не должны быть согласованы при изменении размера или другая операция, тогда как прокладка для каждого UIElement, который будет остаются неизменными при изменении размера окно.

**

 <StackPanel Orientation="Horizontal">
            <Label Padding="10">Foo</Label>
            <TextBox Padding="10">Bar</TextBox>
            <ComboBox Padding="10">
                <TextBlock>Baz</TextBlock>
                <TextBlock>Bat</TextBlock>
            </ComboBox>
            <TextBlock Padding="10">Plugh</TextBlock>
            <TextBlock Padding="10" VerticalAlignment="Bottom">XYZZY</TextBlock>
        </StackPanel>

Здесь я предоставляю внутреннее унифицированное дополнение размером 10 к каждому элементу управления, вы всегда можете играть с ним, чтобы изменить его по отношению к левым, верхним, правым, нижним размерам заполнения.

Without PaddingWith Padding

См. приведенные выше скриншоты для справки (1) Без прокладки и (2) с прокладкой Надеюсь, это может помочь...

Ответ 5

Как я решил это решить, это использовать поля фиксированного размера и дополнения.

Реальная проблема, с которой я столкнулась, заключалась в том, что я позволял пользователям изменять размер шрифта в приложении. Это казалось хорошей идеей для тех, кто пришел к этой проблеме с точки зрения Windows Forms. Но он прикрутил всю макет; поля и отступы, которые выглядели просто отлично с текстом 12pt, выглядели ужасно с текстом 36pt.

Однако с точки зрения WPF гораздо проще (и лучше) выполнить то, что я действительно пытался - пользовательский интерфейс, размер которого пользователь мог настроить в соответствии со своим вкусом, состоял в том, чтобы просто положить ScaleTransform над представлением и привяжите его ScaleX и ScaleY к значению ползунка.

Это не только дает пользователям гораздо более тонкий контроль над размером своего пользовательского интерфейса, но также означает, что все выравнивание и настройка, сделанные для правильного выравнивания элементов, все равно работают независимо от размера пользовательского интерфейса.

Ответ 6

Это сложно, поскольку ComboBox и TextBlock имеют разные внутренние поля. В таких обстоятельствах я всегда оставлял все, чтобы VerticalAlignment как Center выглядел не очень хорошо, но да, вполне приемлемо.

Альтернативой является создание собственного CustomControl, полученного из ComboBox, и инициализация его поля в конструкторе и повторное использование его везде.

Ответ 7

Возможно, это поможет:

<Window x:Class="Wpfcrm.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Wpfcrm"
        mc:Ignorable="d"
        Title="Business" Height="600" Width="1000" WindowStartupLocation="CenterScreen" ResizeMode="NoResize">

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="434*"/>
            <ColumnDefinition Width="51*"/>
            <ColumnDefinition Width="510*"/>
        </Grid.ColumnDefinitions>
        <StackPanel x:Name="mainPanel" Orientation="Vertical" Grid.ColumnSpan="3">
            <StackPanel.Background>
                <RadialGradientBrush>
                    <GradientStop Color="Black" Offset="0"/>
                    <GradientStop Color="White"/>
                    <GradientStop Color="White"/>
                </RadialGradientBrush>
            </StackPanel.Background>

            <DataGrid Name="grdUsers" ColumnWidth="*" Margin="0,-20,0,273" Height="272">

            </DataGrid>

        </StackPanel>

        <StackPanel Orientation="Horizontal" Grid.ColumnSpan="3">

            <TextBox Name="txtName" Text="Name" Width="203" Margin="70,262,0,277"/>
            <TextBox x:Name="txtPass" Text="Pass" Width="205" Margin="70,262,0,277"/>
            <TextBox x:Name="txtPosition" Text="Position" Width="205" Margin="70,262,0,277"/>
        </StackPanel>

        <StackPanel Orientation="Vertical" VerticalAlignment="Bottom" Height="217" Grid.ColumnSpan="3" Margin="263,0,297,0">
            <Button Name="btnUpdate" Content="Update" Height="46" FontSize="24" FontWeight="Bold" FontFamily="Comic Sans MS" Margin="82,0,140,0" BorderThickness="1">
                <Button.Background>
                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="Black"/>
                        <GradientStop Color="#FF19A0AE" Offset="0.551"/>
                    </LinearGradientBrush>
                </Button.Background>


            </Button>
        </StackPanel>

    </Grid>


</Window>