创建WPF中的吉他和弦编辑器(从RichTextBox的?) [英] Create guitar chords editor in WPF (from RichTextBox?)

查看:447
本文介绍了创建WPF中的吉他和弦编辑器(从RichTextBox的?)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

应用程序我工作在WPF的主要目的是允许编辑,因此打印歌曲的歌词与吉他和弦过去。

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.

这似乎并不支持场景的RichTextBox 。这些都是一些问题,我不知道如何解决:

This does not appear to be supported scenario for RichTextBox. These are some of the problems that I don't know how to solve:

  • 和弦都有其固定的位置上在歌词文本一些字符(或更一般 TextPointer 歌词线)。当用户编辑歌词我想和弦留在正确的字符。例如:
  • 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

  • 换行:2号线(1号与和弦二路歌词)在逻辑上是一条线,当涉及到包装。当一个单词换到下一行是在它所有的弦也应该换行。此外,当和弦包装的话,它是在还包裹。例如:
  • 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... 
      

      • 在说我有歌词行钽VA 和弦超过 A 。我想歌词看起来像不喜欢。第二张照片是不是 V A 之间kerned。橘线只是用来可视化效果(但他们记号×偏移其中,和弦将被放置)。用于生产第一个样品code是< TextBlock中的FontFamily =宋体字号=60>钽VA< / TextBlock的> 和第二个样本< TextBlock中的FontFamily =宋体字号=60><跨度>钽V<浮/>在< / SPAN 20 GT;< / TextBlock的>
        • 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>.
        • 如何得到任何想法的RichTextBox 做到这一点?还是有更好的方式来做到这在WPF?我会子类内联运行帮助吗?任何想法,黑客, TextPointer 魔法,code或相关主题的链接是欢迎的。

          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.

          我探讨两个主要的方向,所以我提出新的问题,要解决这个问题,但都会导致另外一个问题:

          I'm exploring 2 major directions to solve this problem but both lead to another problems so I ask new question:

          1. 在试图把的RichTextBox 进入和弦编辑器 - 看一看<一href="http://stackoverflow.com/questions/5798194/how-can-i-create-subclass-of-class-inline-the-one-used-in-flowdocument">How我可以创建类内联的子类?。
          2. 从构建独立的组件新的编辑器,比如面板取值文本框 ES等,在<一个建议href="http://stackoverflow.com/questions/5796457/create-guitar-chords-editor-in-wpf-from-richtextbox/5797833#5797833">H.B.回答。这将需要的很多编码,也导致了下面的(未解决)问题:

          1. Trying to turn RichTextBox into chords editor - Have a look at How can I create subclass of class Inline?.
          2. Build new editor from separate components like Panels TextBoxes etc. as suggested in H.B. answer. This would need a lot of coding and also led to following (unsolved) problems:

          • <一个href="http://stackoverflow.com/questions/5834187/how-lay-out-list-of-components-that-can-change-their-width-height-according-to-th">Components将根据他们的宽度/高度更改为他们布置位置(空白去除的行开始等)
          • <一个href="http://stackoverflow.com/questions/5809498/get-kerning-offset-value-for-given-character-pair-and-font-in-net">Kerning将要手动插入的在组件的边界。
          • <一个href="http://stackoverflow.com/questions/5820578/wpf-how-to-make-richtextbox-look-like-textblock">How使RichTextBox的样子TextBlock的?(不优雅破解/解决方法是已知的)
          • Components will change their Width/Height according to they layout position (white space removal at line beginning etc.)
          • Kerning will have to be inserted manually at components boundaries.
          • How to make RichTextBox look like TextBlock? (not elegant hack/workaround is known)

          <一个href="http://stackoverflow.com/questions/5796457/create-guitar-chords-editor-in-wpf-from-richtextbox/5845927#5845927">Markus许特尔的高质量答案指示我,很多事情可以用的RichTextBox 然后我希望当我试图调整它适合我的需要我自己来完成。我有时间现在只发掘了详细的回答。马库斯可能是的RichTextBox 魔术师,我需要帮我这一点,但也有一些尚未解决的问题,他的解决方案,以及:


          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:

          1. 在这个应用程序将是所有关于美丽字样的歌词。其主要目标是,文字看起来从视图的印刷点的完美。当弦太靠近对方,甚至重叠的马库斯认为,我之前的立场反复添加另外的空间,直到他们的距离就足够了。实际上有规定,即用户可以设置2弦之间的最小距离。这最小距离应得到尊重,而不是超越,直到必要的。空间的粒度不够 - 当我补充需要,我可能会更广泛地再必要的缝隙最后一个空格 - 这将使该文件看坏我不认为这是可以接受的。 我需要插入自定义宽度的空间
          2. 有可能是没有和弦(纯文本)或偶数行,没有文字(仅和弦)线。当 lineHeight是设置为 25 或整个文档等固定值,它会导致无弦线有空线在他们之上。当只有和弦,没有文字会有他们没有空间。
          1. 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.
          2. 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.

          有其他的小问题,但我不是想我能解决他们或我认为他们并不重要。无论如何,我认为马库斯的答案才是真正有价值的 - 不仅为展示我可能要走的路,也为使用的RichTextBox 与装饰器的一般模式示范

          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.

          推荐答案

          我不能给你任何具体的帮助,但在建筑方面,你需要从这个改变你的布局

          I cannot give you any concrete help but in terms of architecture you need to change your layout from this

          要这个

          一切是一个黑客。你的单位/字形必须成为一个字弦对。

          Everything else is a hack. Your unit/glyph must become a word-chord-pair.

          编辑:我一直打打闹闹用模板ItemsControl中,它甚至可以出到一定程度,所以它可能会感兴趣

          I have been fooling around with a templated ItemsControl and it even works out to some degree, so it might be of interest.

          <ItemsControl Grid.IsSharedSizeScope="True" ItemsSource="{Binding SheetData}"
                        Name="_chordEditor">
              <ItemsControl.ItemsPanel>
                  <ItemsPanelTemplate>
                      <WrapPanel/>
                  </ItemsPanelTemplate>
              </ItemsControl.ItemsPanel>
              <ItemsControl.ItemTemplate>
                  <DataTemplate>
                      <Grid>
                          <Grid.RowDefinitions>
                              <RowDefinition SharedSizeGroup="A" Height="Auto"/>
                              <RowDefinition SharedSizeGroup="B" Height="Auto"/>
                          </Grid.RowDefinitions>
                          <Grid.Children>
                              <TextBox Name="chordTB" Grid.Row="0" Text="{Binding Chord}"/>
                              <TextBox Name="wordTB"  Grid.Row="1" Text="{Binding Word}"
                                       PreviewKeyDown="Glyph_Word_KeyDown" TextChanged="Glyph_Word_TextChanged"/>
                          </Grid.Children>
                      </Grid>
                  </DataTemplate>
              </ItemsControl.ItemTemplate>
          </ItemsControl>
          

          private readonly ObservableCollection<ChordWordPair> _sheetData = new ObservableCollection<ChordWordPair>();
          public ObservableCollection<ChordWordPair> SheetData
          {
              get { return _sheetData; }
          }
          

          public class ChordWordPair: INotifyPropertyChanged
          {
              private string _chord = String.Empty;
              public string Chord
              {
                  get { return _chord; }
                  set
                  {
                      if (_chord != value)
                      {
                          _chord = value;
                          // This uses some reflection extension method,
                          // a normal event raising method would do just fine.
                          PropertyChanged.Notify(() => this.Chord);
                      }
                  }
              }
          
              private string _word = String.Empty;
              public string Word
              {
                  get { return _word; }
                  set
                  {
                      if (_word != value)
                      {
                          _word = value;
                          PropertyChanged.Notify(() => this.Word);
                      }
                  }
              }
          
              public ChordWordPair() { }
              public ChordWordPair(string word, string chord)
              {
                  Word = word;
                  Chord = chord;
              }
          
              public event PropertyChangedEventHandler PropertyChanged;
          }
          

          private void AddNewGlyph(string text, int index)
          {
              var glyph = new ChordWordPair(text, String.Empty);
              SheetData.Insert(index, glyph);
              FocusGlyphTextBox(glyph, false);
          }
          
          private void FocusGlyphTextBox(ChordWordPair glyph, bool moveCaretToEnd)
          {
              var cp = _chordEditor.ItemContainerGenerator.ContainerFromItem(glyph) as ContentPresenter;
              Action focusAction = () =>
              {
                  var grid = VisualTreeHelper.GetChild(cp, 0) as Grid;
                  var wordTB = grid.Children[1] as TextBox;
                  Keyboard.Focus(wordTB);
                  if (moveCaretToEnd)
                  {
                      wordTB.CaretIndex = int.MaxValue;
                  }
              };
              if (!cp.IsLoaded)
              {
                  cp.Loaded += (s, e) => focusAction.Invoke();
              }
              else
              {
                  focusAction.Invoke();
              }
          }
          
          private void Glyph_Word_TextChanged(object sender, TextChangedEventArgs e)
          {
              var glyph = (sender as FrameworkElement).DataContext as ChordWordPair;
              var tb = sender as TextBox;
          
              string[] glyphs = tb.Text.Split(' ');
              if (glyphs.Length > 1)
              {
                  glyph.Word = glyphs[0];
                  for (int i = 1; i < glyphs.Length; i++)
                  {
                      AddNewGlyph(glyphs[i], SheetData.IndexOf(glyph) + i);
                  }
              }
          }
          
          private void Glyph_Word_KeyDown(object sender, KeyEventArgs e)
          {
              var tb = sender as TextBox;
              var glyph = (sender as FrameworkElement).DataContext as ChordWordPair;
          
              if (e.Key == Key.Left && tb.CaretIndex == 0 || e.Key == Key.Back && tb.Text == String.Empty)
              {
                  int i = SheetData.IndexOf(glyph);
                  if (i > 0)
                  {
                      var leftGlyph = SheetData[i - 1];
                      FocusGlyphTextBox(leftGlyph, true);
                      e.Handled = true;
                      if (e.Key == Key.Back) SheetData.Remove(glyph);
                  }
              }
              if (e.Key == Key.Right && tb.CaretIndex == tb.Text.Length)
              {
                  int i = SheetData.IndexOf(glyph);
                  if (i < SheetData.Count - 1)
                  {
                      var rightGlyph = SheetData[i + 1];
                      FocusGlyphTextBox(rightGlyph, false);
                      e.Handled = true;
                  }
              }
          }
          

          起初一些符号应加入到集合,否则就没有输入字段(这也可避免进一步的模板,例如使用datatrigger显示的字段,如果集合为空)。

          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.

          这篇关于创建WPF中的吉他和弦编辑器(从RichTextBox的?)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆