Sorry if the title a bit misleading, I have difficulty to entitle it. It's also not exactly "center". (see below for further explanations)
Hi, I am going to make a project to write numbered musical scores. But I found an obstacle on leveling the "font". (See below picture for visual)
- I need
1 2 3 3 1 2 3 4 4
to be in a straight "line" - I use
WrapPanel
as the container of my notes (as in the screen).
The main problem is on the pitch dot, which located EITHER above or below the note. Pitch is bound to the note, so I need process it in 1 User Control. But, if so, then I can't control the placement of the note "font" to be in one line.
Below is my note user control code :
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;
}
}
}
}
Note :
- I'm not bound to that code, so if any of you have dealt to things like this more efficiently with totally different approach, then it's always welcome.
- Sorry for some grammar error, if any, since english is not my first language.
- I feel that I have not explained my problem real clear, since I also quite confused on it, so, please clarify anything you need.
Thanks
ADDITIONAL DETAILS :
- The dot theoretically doesn't have certain limitation, but in practice, it's usually only 3 maximum (either 3 on top or 3 on bottom - not both)
- In my code above, the note user control is divided into 3 grid row, the top one is for stacking the top dot, the mid one is for visualizing the note (numbers) and the bot one is for stacking the bot dot.
- Please clarify anything, and I'll add more.
You should do this with an ItemsControl which use an appropriate
ItemTemplate
for the numeric notes.First, create a ViewModel that defines a collection of "numeric note" items:
In the constructor of your MainWindow, you may set the DataContext to an instance of this ViewModel like this:
For visualizing the pitch I'd recommend to use a
Path
object with a Geometry that consists of a number of EllipseGeometries. To achieve this you would implement a binding converter that converts the pitch number into a Geometry, like shown below. It uses theparameter
argument of the Convert method to create a Geometry for either positive or negative pitch values.Now, in XAML you would create an instance of this converter in e.g. the MainWindow
Resources
:and write the ItemsControl like shown below. Note that the DataTemplate uses a fixed height for the Path elements that show the top and bottom pitch. If you need to show more than three dots, you'd need to increase their height.