使用 WPF 的内联文本框标签

发布于 2024-08-26 13:12:55 字数 681 浏览 4 评论 0原文

我正在尝试在 WPF 应用程序中重现某些纸质表单的布局。文本框的标签应与文本框的内容“内联”,而不是像普通 Windows 窗体那样“在外部”。因此,使用 Xxxxxx 标签:

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

Xxxxxx 无法编辑,如果用户选择文本框的所有内容,则标签必须保持未选中状态,我需要能够单独设置标签的文本颜色/格式的样式,当有文本框中没有文本,但它有焦点,插入符号应该在标签后面闪烁,我需要文本框中文本的基线和标签对齐。

我尝试的一种解决方案是将文本块部分放在输入上,然后使用文本缩进来缩进可编辑文本,但这会导致以下段落出现问题,因为它们也缩进了。我不知道如何缩进第一段。它需要一些摆弄才能使文本对齐 - 更可靠的设置将是理想的。

那么,关于如何设置它有什么建议吗?

谢谢

I'm trying to reproduce the layout of some paper forms in a WPF application. Labels for text boxes are to be "inline" with the content of the text boxes, rather than "outside" like normal Windows forms. So, with an Xxxxxx label:

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

The Xxxxxx cannot be editable, if the user selects all the content of the text box, the label must remain unselected, I need to be able to style the text colour/formatting of the label separately, when there is no text in the text box, but it has focus, the caret should flash just after the label, and I need the baselines of the text in the text box and the label to line up.

One solution I tried was putting a textblock partially over the input, then using text indent to indent the editable text, though this caused problems with following paragraphs, since they were indented too. I'm not sure how to indent just the first paragraph. It required some fiddling to get the text to line up - a more reliable setup would be ideal.

So, any suggestions on how to set this up?

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

烟燃烟灭 2024-09-02 13:12:55

好吧,我可以建议一种有点黑客的方法来做到这一点。

首先,请注意,您可以将 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 事件。在事件中,找到 RTB 文档中的第一个 InlineUIContainer,如果 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 上,它将丢失选择。我确信这个问题可以解决。但效果很好。它甚至可以处理用户使用箭头键导航的情况。

就这么多,你几乎就可以到达那里了。不过,另一件事可能会让您感到困惑,那就是用户是否将光标定位在文本的最开头并按退格键。

处理该问题需要类似的操作:将插入符号位置与第一个 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;
    }            
}

Well, I can suggest a somewhat hackish way to do it.

First, note that you can put UI elements into a FlowDocument. So that makes something like this possible:

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

The problem now becomes keeping the user from editing the InlineUIContainer. That's really two problems.

The first problem is keeping the user from selecting it. To do that, you have to handle the SelectionChanged event. In the event, find the first InlineUIContainer in the RTB's document, and if Selection.Start is before that, change it.

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

There's probably an easier way to formulate that LINQ query, but I kind of like it. And this isn't 100% perfect; if you select inside the text and drag left over the TextBlock, it will lose the selection. I'm sure that can be fixed. But it works pretty well. It even handles the case where the user navigates around with arrow keys.

Just this much gets you almost all the way there. The other thing that can mess you up, though, is if the user positions the cursor at the very beginning of the text and presses BACKSPACE.

Handling that requires something similar: compare the caret position with the end of the first InlineUIElement, and cancel the BACKSPACE (by marking the event as handled) if the caret's at that position:

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;
    }            
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文