Встроенные метки текстовых полей с WPF

Я пытаюсь воспроизвести макет некоторых бумажных форм в приложении WPF. Ярлыки для текстовых полей должны быть «встроенными» с содержимым текстовых полей, а не «внешними», как в обычных формах Windows. Итак, с меткой Xxxxxx:

+-----------------------------+
| Xxxxxx: some text written   |
| in the multiline input.     |
|                             |
| another paragraph continues |
| without indentation.        |
|                             |
|                             |
+-----------------------------+

Xxxxxx не может быть редактируемым, если пользователь выбирает все содержимое текстового поля, метка должна оставаться невыделенной, мне нужно иметь возможность стилизовать цвет текста / форматирование метки отдельно, когда в текстовом поле нет текста , но у него есть фокус, курсор должен мигать сразу после метки, и мне нужно, чтобы базовые линии текста в текстовом поле и метка выровнялись.

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

Итак, есть какие-нибудь предложения о том, как это настроить?

Спасибо


person Douglas    schedule 26.03.2010    source источник


Ответы (1)


Что ж, я могу предложить несколько хакерский способ сделать это.

Во-первых, обратите внимание, что вы можете помещать элементы пользовательского интерфейса в FlowDocument. Это делает возможным что-то вроде этого:

<RichTextBox>
  <FlowDocument>
    <Paragraph>
      <InlineUIContainer>
        <TextBlock>This is your label: </TextBlock>
      </InlineUIContainer>
      <Run>And this is the editable text.</Run>
    </Paragraph>
  </FlowDocument>
</RichTextBox>

Теперь проблема заключается в том, чтобы помешать пользователю редактировать InlineUIContainer. Это действительно две проблемы.

Первая проблема - удерживать пользователя от выбора его. Для этого вам нужно обработать событие SelectionChanged. В таком случае найдите первый InlineUIContainer в документе RTB, и, если Selection.Start находится до этого, измените его.

private void RichTextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
    RichTextBox rtb = (RichTextBox) sender;
    if (rtb == null) return;

    InlineUIContainer c = rtb.Document
        .Blocks
        .Where(x => x is Paragraph)
        .Cast<Paragraph>()
        .SelectMany(x => x.Inlines)
        .Where(x => x is InlineUIContainer)
        .Cast<InlineUIContainer>()
        .FirstOrDefault();

    if (c == null) return;

    if (rtb.Selection.Start.CompareTo(c.ElementEnd) < 0)
    {
        rtb.Selection.Select(c.ElementEnd, rtb.Selection.End);
    }
}

Вероятно, есть более простой способ сформулировать этот запрос LINQ, но мне он вроде как нравится. И это не на 100% идеально; если выделить внутри текста и перетащить влево над TextBlock, выделение будет потеряно. Я уверен, что это можно исправить. Но работает неплохо. Он даже обрабатывает случай, когда пользователь перемещается с помощью клавиш со стрелками.

Просто так вы почти до конца доберетесь до цели. Еще одна вещь, которая может вас запутать, - это если пользователь помещает курсор в самое начало текста и нажимает BACKSPACE.

Обработка, требующая чего-то подобного: сравните позицию курсора с концом первого InlineUIElement и отмените BACKSPACE (пометив событие как обработанное), если курсор находится в этой позиции:

private void RichTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
    if (e.Key != Key.Back)
    {
        return;
    }

    RichTextBox rtb = (RichTextBox)sender;
    if (rtb == null) return;

    InlineUIContainer c = rtb.Document
        .Blocks
        .Where(x => x is Paragraph)
        .Cast<Paragraph>()
        .SelectMany(x => x.Inlines)
        .Where(x => x is InlineUIContainer)
        .Cast<InlineUIContainer>()
        .FirstOrDefault();

    if (c == null) return;

    if (rtb.CaretPosition.CompareTo(c.ElementEnd.GetInsertionPosition(LogicalDirection.Forward)) <= 0)
    {
        e.Handled = true;
    }            
}
person Robert Rossney    schedule 27.03.2010