我在 WPF 中开发的应用程序的主要目的是允许编辑并随后打印带有吉他和弦的歌词。
即使您不演奏任何乐器,您也可能见过和弦。为了给你一个想法,它看起来像这样:
E E6
I know I stand in line until you
E E6 F#m B F#m B
think you have the time to spend an evening with me
但是我想要使用 Times New Roman
字体,而不是这种丑陋的等距字体,并且歌词和和弦(和弦以粗体显示)都带有字距调整。我希望用户能够编辑它。
RichTextBox
似乎不支持这种情况。这些是我不知道如何解决的一些问题:
- 和弦的位置固定在歌词文本中的某些字符上(或者更一般地说歌词行的
TextPointer
)。当用户编辑歌词时,我希望和弦保留在正确的字符上。例子:
。
E E6
I know !!!SOME TEXT REPLACED HERE!!! in line until you
- 换行:在换行时,2 行(第一行是和弦,第二行是歌词)在逻辑上是一行。当一个单词换行到下一行时,它上面的所有和弦也应该换行。另外,当和弦环绕单词时,它也会环绕该单词。例子:
。
E E6
think you have the time to spend an
F#m B F#m B
evening with me
- 即使和弦彼此太接近,和弦也应该保持在正确的字符上。在这种情况下,歌词行中会自动插入一些额外的空格。例子:
。
F#m E6
...you have the ti me to spend...
- 假设我有歌词行
Ta VA
和和弦 A
。我希望歌词看起来像 而不是 。第二张图片未在 V
和 A
之间调整字距。橙色线只是为了可视化效果(但它们标记了和弦放置的 x 偏移量)。用于生成第一个示例的代码为 Ta VA
,用于生成第二个示例的代码为 Ta V
。
关于如何让 RichTextBox
执行此操作有什么想法吗?或者在WPF中有更好的方法吗?我可以对 Inline
或 Run
进行子类化吗?欢迎任何想法、技巧、TextPointer
魔法、代码或相关主题的链接。
编辑:
我正在探索解决这个问题的两个主要方向,但这两个方向都会导致另一个问题,所以我提出新问题:
- 尝试将
RichTextBox
变成和弦编辑器 - 看看 如何创建 Inline 类的子类?。
-
按照 HB 答案。这需要大量编码,并且还会导致以下(未解决的)问题:
Edit#2
Markus Hütter 的高质量答案 向我展示了当我尝试根据自己的需要调整它时,使用 RichTextBox
可以完成更多的事情。我现在才有时间详细探讨答案。 Markus 可能是 RichTextBox
魔术师,我需要帮助我解决这个问题,但他的解决方案也存在一些未解决的问题:
- 这个应用程序将全部是关于“漂亮”打印的歌词。主要目标是从印刷的角度来看文本看起来很完美。当和弦彼此距离太近甚至重叠时,Markus 建议我在其位置之前迭代地添加附加空格,直到它们的距离足够。实际上要求用户可以设置两个和弦之间的最小距离。应遵守该最小距离,除非必要,否则不得超过。空格不够细粒度 - 一旦我添加了所需的最后一个空格,我可能会使间隙比必要的更宽 - 这将使文档看起来“糟糕”,我认为它不会被接受。 我需要插入自定义宽度的空间。
- 可能存在没有和弦(只有文本)的行,甚至可能没有文本(只有和弦)的行。当
LineHeight
设置为 25
或整个文档的其他固定值时,将导致没有和弦的行上方有“空行”。当只有和弦而没有文本时,就没有空间容纳它们。
还有其他一些小问题,但我要么认为我可以解决它们,要么认为它们不重要。无论如何,我认为 Markus 的答案非常有价值 - 不仅向我展示了可能的方法,而且还演示了将 RichTextBox
与装饰器一起使用的一般模式。
Main purpose of application I'm working on in WPF is to allow editing and consequently printing of songs lyrics with guitar chords over it.
You have probably seen chords even if you don't play any instrument. To give you an idea it looks like this:
E E6
I know I stand in line until you
E E6 F#m B F#m B
think you have the time to spend an evening with me
But instead of this ugly mono-spaced font I want to have Times New Roman
font with kerning for both lyrics and chords (chords in bold font). And I want user to be able to edit this.
This does not appear to be supported scenario for RichTextBox
. These are some of the problems that I don't know how to solve:
- Chords have their positions fixed over some character in lyrics text (or more generally
TextPointer
of lyrics line). When user edits lyrics I want chord to stay over right character. Example:
.
E E6
I know !!!SOME TEXT REPLACED HERE!!! in line until you
- Line wrapping: 2 lines (1th with chords and 2th with lyrics) are logically one line when it comes to wrapping. When a word wraps to next line all chords that are over it should also wrap. Also when chord wraps the word that it is over it also wrap. Example:
.
E E6
think you have the time to spend an
F#m B F#m B
evening with me
- Chords should stay over right character even when chords are too near to each other. In this case some extra space is automatically inserted in lyrics line. Example:
.
F#m E6
...you have the ti me to spend...
- Say I have lyrics line
Ta VA
and chord over A
. I want the lyrics to look like not like . Second picture is not kerned between V
and A
. Orange lines are there only to visualize the effect (but they mark x offsets where chord would be placed). Code used to produce first sample is <TextBlock FontFamily="Times New Roman" FontSize="60">Ta VA</TextBlock>
and for second sample <TextBlock FontFamily="Times New Roman" FontSize="60"><Span>Ta V<Floater />A</Span></TextBlock>
.
Any ideas on how to get RichTextBox
to do this ? Or is there better way to do it in WPF? Will I sub-classing Inline
or Run
help? Any ideas, hacks, TextPointer
magic, code or links to related topics are welcome.
Edit:
I'm exploring 2 major directions to solve this problem but both lead to another problems so I ask new question:
- Trying to turn
RichTextBox
into chords editor - Have a look at How can I create subclass of class Inline?.
-
Build new editor from separate components like Panel
s TextBox
es etc. as suggested in H.B. answer. This would need a lot of coding and also led to following (unsolved) problems:
Edit#2
Markus Hütter's high quality answer has shown me that a lot more can be done with RichTextBox
then I expected when I was trying to tweak it for my needs myself. I've had time to explore the answer in details only now. Markus might be RichTextBox
magician I need to help me with this but there are some unsolved problems with his solution as well:
- This application will be all about "beautifully" printed lyrics. The main goal is that the text looks perfect from the typographic point of view. When chords are too near to each other or even overlapping Markus suggests that I iteratively add addition spaces before its position until their distance is sufficient. There is actually requirement that the user can set minimum distance between 2 chords. That minimum distance should be honored and not exceeded until necessary. Spaces are not granular enough - once I add last space needed I'll probably make the gap wider then necessary - that will make the document look 'bad' I don't think it could be accepted. I'd need to insert space of custom width.
- There could be lines with no chords (only text) or even lines with no text (only chords). When
LineHeight
is set to 25
or other fixed value for whole document it will cause lines with no chords to have "empty lines" above them. When there are only chords and no text there will be no space for them.
There are other minor problems but I either think I can solve them or I consider them not important. Anyway I think Markus's answer is really valuable - not only for showing me possible way to go but also as a demonstration of general pattern of using RichTextBox
with adorner.
发布评论
评论(2)
我无法给您任何具体的帮助,但在架构方面,您需要将布局从
更改为
其他一切都是黑客行为。您的单位/字形必须成为单词和弦对。
编辑:我一直在玩弄模板化的ItemsControl,它甚至在某种程度上发挥了作用,所以它可能很有趣。
最初应该将一些字形添加到集合中,否则将没有输入字段(这可以通过进一步模板化来避免,例如,通过使用在集合为空时显示字段的数据触发器)。
完善这一点需要大量额外的工作,例如设计文本框的样式、添加书面换行符(目前仅在换行面板进行换行时才会中断)、支持跨多个文本框的选择等。
I cannot give you any concrete help but in terms of architecture you need to change your layout from this
To this
Everything else is a hack. Your unit/glyph must become a word-chord-pair.
Edit: I have been fooling around with a templated ItemsControl and it even works out to some degree, so it might be of interest.
Initially some glyph should be added to the collection, otherwise there will be no input field (this can be avoided with further templating, e.g. by using a datatrigger that shows a field if the collection is empty).
Perfecting this would require a lot of additional work like styling the TextBoxes, adding written line breaks (right now it only breaks when the wrap panel makes it), supporting selection accross multiple textboxes, etc.
Soooo,我在这里玩得很开心。它看起来像这样:
歌词是完全可编辑的,和弦目前还不能编辑(但这将是一个简单的扩大)。
这是xaml:
这是代码:
使用这个装饰器:
这是使用像大卫建议的装饰器,但我知道很难找到如何使用它。那可能是因为没有。我之前花了几个小时在反射器中试图找到表明流程文档的布局已经弄清楚的确切事件。
我不确定构造函数中的调度程序调用是否确实需要,但我将其保留是为了防弹。 (我需要这个,因为在我的设置中,RichTextBox 尚未显示)。
显然这需要更多的编码,但这将为您提供一个开始。你会想要尝试一下定位等。
为了在两个装饰器太近且重叠的情况下获得正确的定位,我建议您以某种方式跟踪哪个装饰器位于前面,并查看当前的装饰器是否会重叠。那么您可以例如在
_position
-TextPointer 之前迭代地插入一个空格。如果您稍后决定,您也希望和弦可编辑,您可以在装饰器下拥有整个 VisualTree,而不是仅在 OnRender 中绘制文本。 (此处是下面带有 ContentControl 的装饰器示例)。但请注意,您必须处理 ArrangeOveride,然后才能通过
_position
CharacterRect 正确定位装饰器。Soooo, I had a little fun here. This is how it looks like:
The lyrics is fully editable, the chords are currently not (but this would be an easy extension).
this is the xaml:
and this is the code:
using this Adorner:
this is using an adorner like david suggested, but I know it is hard to find a how to out there. That's probably because there is none. I had spent hours before in reflector trying to find that exact event that signals that the layout of the flowdocument has been figured out.
I'm not sure if that dispatcher call in the constructor is actually needed, but I left it in for being bulletproof. (I needed this because in my setup the RichTextBox had not been shown yet).
Obviously this needs a lot more coding, but this will give you a start. You will want to play around with positioning and such.
For getting the positioning right if two adorners are too close and are overlapping I'd suggest you somehow keep track of which adorner comes before and see if the current one would overlap. then you can for example iteratively insert a space before the
_position
-TextPointer.If you later decide, you want the chords editable too, you can instead of just drawing the text in OnRender have a whole VisualTree under the adorner. (here is an example of an adorner with a ContentControl underneath). Beware though that you have to handle the ArrangeOveride then to correctly position the Adorner by the
_position
CharacterRect.