【问题标题】:How to Vertically "center" align the multi line text如何垂直“居中”对齐多行文本
【发布时间】:2014-06-17 18:18:00
【问题描述】:

对不起,如果标题有点误导,我很难给它命名。它也不完全是“中心”。 (更多解释见下文)

你好,我打算做一个项目来写有编号的乐谱。但是我在调​​平“字体”时发现了一个障碍。 (视觉效果见下图)

  1. 我需要1 2 3 3 1 2 3 4 4 在一条直线上
  2. 我使用WrapPanel 作为我的笔记的容器(如屏幕所示)。

主要问题在于音高点,它位于音符上方或下方EITHER。音高绑定到音符,所以我需要在 1 User Control 中处理它。但是,如果是这样,那么我无法将注释“字体”的位置控制在一行中。

下面是我的笔记用户控制代码:

XAML

<UserControl x:Class="RevisiConverter.NumericNoteBox"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" Width="{Binding ActualWidth, ElementName=txbNote}" 
         Height="{Binding ActualHeight, ElementName=mainStack}">
<StackPanel Name="mainStack">
    <StackPanel Name="topStack">

    </StackPanel>
    <Canvas Name="canvas" Width="{Binding ActualWidth, ElementName=txbNote}" Height="{Binding ActualHeight, ElementName=txbNote}"
            HorizontalAlignment="Center">            
        <TextBlock Name="txbNote" Text="1" Foreground="Black" FontSize="15"
                   Margin="0,0,0,-3" FontFamily="Courier" Canvas.Top="0" Canvas.Left="0"/>
    </Canvas>
    <StackPanel Name="botStack">

    </StackPanel>
</StackPanel>

XAML.CS

public partial class NumericNoteBox : UserControl
{
    private Note _child;
    private Line _sharp;

    public Note Child
    {
        get { return _child; }
        set
        {
            _child = value;
            Update();
        }
    }

    public NumericNoteBox()
    {
        InitializeComponent();

        _sharp = null;
    }

    public void Update()
    {
        txbNote.Text = _child.MainNote.ToString();

        if (_sharp != null)
        {
            _sharp.Visibility = Visibility.Hidden;
        }

        topStack.Children.Clear();
        botStack.Children.Clear();

        if (_child != null)
        {
            if (_child.Pitch > 0)
            {
                for (int i = 0; i < _child.Pitch; i++)
                {
                    topStack.Children.Add(new Ellipse());
                    (topStack.Children[topStack.Children.Count - 1] as Ellipse).Width = 3;
                    (topStack.Children[topStack.Children.Count - 1] as Ellipse).Height = 3;
                    (topStack.Children[topStack.Children.Count - 1] as Ellipse).Fill = Brushes.Black;

                    if (_child.Accidental != Note.Accidentals.Flat)
                    {
                        (topStack.Children[topStack.Children.Count - 1] as Ellipse).Margin = new Thickness(0, 1, 0, 0);
                    }
                    else
                    {
                        (topStack.Children[topStack.Children.Count - 1] as Ellipse).Margin = new Thickness(8, 1, 0, 0);
                    }
                }
            }
            else if (_child.Pitch < 0)
            {
                for (int i = 0; i < Math.Abs(_child.Pitch); i++)
                {
                    botStack.Children.Add(new Ellipse());
                    (botStack.Children[botStack.Children.Count - 1] as Ellipse).Width = 3;
                    (botStack.Children[botStack.Children.Count - 1] as Ellipse).Height = 3;
                    (botStack.Children[botStack.Children.Count - 1] as Ellipse).Fill = Brushes.Black;

                    if (_child.Accidental != Note.Accidentals.Flat)
                    {
                        (botStack.Children[botStack.Children.Count - 1] as Ellipse).Margin = new Thickness(0, 1, 0, 0);
                    }
                    else
                    {
                        (botStack.Children[botStack.Children.Count - 1] as Ellipse).Margin = new Thickness(8, 1, 0, 0);
                    }
                }
            }

            if (_child.Accidental == Note.Accidentals.Flat)
            {
                txbNote.Text = "b" + _child.MainNote.ToString();
            }
            else if (_child.Accidental == Note.Accidentals.Sharp)
            {
                if (_sharp == null)
                {
                    _sharp = new Line();
                    _sharp.X1 = 10;
                    _sharp.Y1 = 2.5;
                    _sharp.X2 = -2.5;
                    _sharp.Y2 = 12.5;
                    _sharp.StrokeThickness = 1;
                    _sharp.Stroke = Brushes.Black;

                    canvas.Children.Add(_sharp);
                }

                _sharp.Visibility = Visibility.Visible;
            }
        }
    }
}

注意:

  1. 我不受该代码的约束,因此,如果你们中的任何人用完全不同的方法更有效地处理了类似的事情,那么它总是受欢迎的。
  2. 对于一些语法错误(如果有的话),我们深表歉意,因为英语不是我的第一语言。
  3. 我觉得我没有真正清楚地解释我的问题,因为我对此也很困惑,所以,请澄清你需要的任何东西。

谢谢

其他细节:

  1. 点理论上没有一定的限制,但在实践中,它通常最多只有 3 个(顶部 3 个或底部 3 个 - 不能同时使用)
  2. 在我上面的代码中,note 用户控件分为 3 个网格行,顶部用于堆叠顶部点,中间用于可视化笔记(数字),bot 用于堆叠 bot 点.
  3. 请澄清任何事情,我会补充更多。

【问题讨论】:

  • 数字上方和下方的最大点数是多少,它们必须如何放置?这一切都应该通过 ItemsControl 的正确 ItemTemplate 来完成。
  • @Clemens 感谢您的澄清。我在 OP 的底部做了一些额外的细节,请查看。谢谢。

标签: c# wpf user-controls


【解决方案1】:

您应该使用 ItemsControl 来执行此操作,该控件使用适当的 ItemTemplate 作为数字注释。

首先,创建一个定义“数字注释”项目集合的 ViewModel:

public class NumericNoteItem
{
    public int Number { get; set; }
    public int Pitch { get; set; }
}

public class ViewModel
{
    public ViewModel()
    {
        NumericNotes = new ObservableCollection<NumericNoteItem>();
    }

    public ObservableCollection<NumericNoteItem> NumericNotes { get; private set; }
}

在 MainWindow 的构造函数中,您可以将 DataContext 设置为此 ViewModel 的实例,如下所示:

public MainWindow()
{
    InitializeComponent();

    var vm = new ViewModel();
    vm.NumericNotes.Add(new NumericNoteItem { Number = 1, Pitch = 0 });
    vm.NumericNotes.Add(new NumericNoteItem { Number = 2, Pitch = 1 });
    vm.NumericNotes.Add(new NumericNoteItem { Number = 3, Pitch = -1 });
    vm.NumericNotes.Add(new NumericNoteItem { Number = 4, Pitch = 0 });
    vm.NumericNotes.Add(new NumericNoteItem { Number = 5, Pitch = 2 });
    vm.NumericNotes.Add(new NumericNoteItem { Number = 6, Pitch = -2 });
    vm.NumericNotes.Add(new NumericNoteItem { Number = 4, Pitch = 0 });
    vm.NumericNotes.Add(new NumericNoteItem { Number = 5, Pitch = 3 });
    vm.NumericNotes.Add(new NumericNoteItem { Number = 6, Pitch = -3 });

    DataContext = vm;
}

为了使音高可视化,我建议使用 Path 对象和由多个 EllipseGeometry 组成的 Geometry。为此,您将实现一个绑定转换器,将音高数字转换为几何图形,如下所示。它使用 Convert 方法的 parameter 参数为正或负间距值创建几何。

public class NotePitchConverter : IValueConverter
{
    private const double radius = 1.5;
    private const double distance = 2 * radius + 1;

    public object Convert(
        object value, Type targetType, object parameter, CultureInfo culture)
    {
        var pitch = (int)value;
        var geometry = new GeometryGroup();

        if (parameter as string == "Bottom")
        {
            pitch = -pitch;
        }

        for (int i = 0; i < pitch; i++)
        {
            geometry.Children.Add(new EllipseGeometry(
                new Point(radius, radius + i * distance), radius, radius));
        }

        return geometry;
    }

    public object ConvertBack(
        object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

现在,在 XAML 中,您将创建此转换器的实例,例如主窗口Resources:

<Window.Resources>
    <local:NotePitchConverter x:Key="NotePitchConverter"/>
</Window.Resources>

并编写如下所示的 ItemsControl。请注意,DataTemplate 对显示顶部和底部间距的 Path 元素使用固定高度。如果您需要显示三个以上的点,则需要增加它们的高度。

<ItemsControl ItemsSource="{Binding NumericNotes}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid Margin="2">
                <Grid.RowDefinitions>
                    <RowDefinition Height="12"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="12"/>
                </Grid.RowDefinitions>
                <Path Grid.Row="0" Fill="Black" HorizontalAlignment="Center"
                      VerticalAlignment="Bottom"
                      Data="{Binding Pitch,
                             Converter={StaticResource NotePitchConverter}}"/>
                <TextBlock Grid.Row="1" Text="{Binding Number}"/>
                <Path Grid.Row="2" Fill="Black" HorizontalAlignment="Center"
                      VerticalAlignment="Top"
                      Data="{Binding Pitch,
                             Converter={StaticResource NotePitchConverter},
                             ConverterParameter=Bottom}"/>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

【讨论】:

  • 我需要多研究一下你的代码,因为对我来说有很多新东西。但是为了简化起见,您的意思是首先为点制作一个固定高度的面板(在本例中为Cell),对吧? (所以高度将保持相等)
  • 正是如此,另外你不应该在代码中操作 UI 元素,而是让自己熟悉 MVVM。
  • 我知道这不是主题,但 XAML.CS 不是也是 ViewModel 吗?还是抓不住。在代码中操作 UI 元素是什么意思?所以我不能,例如,将 StackPanel 添加到网格单元?
  • 其实不是。在真正的 MVVM WPF 应用程序中,您既不会在代码中创建 UI 元素(如 new Ellipse()),也不会将它们添加到容器控件(如 topStack.Children.Add(...))。相反,所有这些动态的东西都将在视图模型中表示,并通过数据模板(例如在 ItemsControl 中)进行可视化。
  • 这就是软件架构。在这里,它用于将视图与逻辑分离。您可以在网上搜索MVVM。有很多资源可以开始阅读。
猜你喜欢
  • 2021-07-19
  • 1970-01-01
  • 1970-01-01
  • 2013-06-06
  • 2013-05-25
  • 1970-01-01
  • 2014-05-01
  • 2012-01-27
  • 1970-01-01
相关资源
最近更新 更多