Встроенные надписи в текстовом поле с WPF - PullRequest
5 голосов
/ 27 марта 2010

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

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

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

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

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

Спасибо

1 Ответ

2 голосов
/ 27 марта 2010

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

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

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

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

Первая проблема заключается в том, что пользователь не может выбрать it. Для этого вам нужно обработать событие 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;
    }            
}
...