Вградени етикети на текстово поле с WPF

Опитвам се да възпроизведа оформлението на някои хартиени формуляри в WPF приложение. Етикетите за текстови полета трябва да бъдат „вградени“ със съдържанието на текстовите полета, а не „извън“, като нормалните формуляри на Windows. И така, с етикет Xxxxxxx:

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

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

Едно решение, което опитах, беше да поставя текстов блок частично върху входа, след което да използвам отстъп на текста за отстъп на редактируемия текст, въпреки че това създаваше проблеми със следващите абзаци, тъй като те също бяха с отстъп. Не съм сигурен как да направя отстъп само на първия абзац. Изискваше се малко работа, за да се подреди текстът - по-надеждна настройка би била идеална.

И така, някакви предложения как да настроите това?

Благодаря


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


Отговори (1)


Е, мога да предложа малко хакерски начин да го направите.

Първо имайте предвид, че можете да поставите UI елементи в 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