所以这是我上次问题How to Vertically “center” align the multi line text
中的一些跟进问题最后一次是来自回答者的评论:
正是这样,加上你不应该操纵代码中的UI元素,而是让自己熟悉MVVM
回答者也使用EllipseGeometry
和Path
很好地回答了问题。但是,因为我对Path
并不熟悉,所以我试图找到另一种更符合我风格的方式。
我发现了另一种方法:
转换器类
public class NoteOctaveConverter : IValueConverter
{
private const double _dotDiameter = 3;
private readonly Thickness _dotMargin = new Thickness(0.25);
private SolidColorBrush _dotFill;
private readonly HorizontalAlignment _dotHzAlign = HorizontalAlignment.Center;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int octave = (int)value;
List<Ellipse> dots = new List<Ellipse>();
StackPanel stack = new StackPanel();
if ((octave > 0 && parameter as string == "TOP") || (octave < 0 && parameter as string == "BOT"))
{
_dotFill = Brushes.Black;
}
else
{
_dotFill = Brushes.Transparent;
}
for (int i = 0; i < Math.Abs(octave); i++)
{
dots.Add(new Ellipse());
dots[i].Width = _dotDiameter;
dots[i].Height = _dotDiameter;
dots[i].Margin = _dotMargin;
dots[i].Fill = _dotFill;
dots[i].HorizontalAlignment = _dotHzAlign;
stack.Children.Add(dots[i]);
}
return stack;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
这就是我将其呈现给视图的方式:
<ContentPresenter Grid.Column="1" Grid.Row="2"
Content="{Binding Path=MusicalNotation.Octave, Converter={StaticResource NoteOctaveConverter}, ConverterParameter=BOT, Mode=OneWay}"
HorizontalAlignment="Center" VerticalAlignment="Top"/>
Idk如果这是回答者的意思是“不操纵代码中的UI元素”(我是MVVM的新手),但是,从MVVM的角度来看,这是否可以接受 ,因为我想要尝试以一种好的方式使用MVVM?
*)这样=使用StackPanel
从Converter
展示ContentPresenter
。
附加说明:
我使用ContentPresenter
方式而不是Path
的原因是我需要很好地理解我的工作,因为我可能必须在演示会上将它呈现给我的讲师(我不是想要呈现一些新的(有风险的), IF 我有另一种可能对我来说更舒服的方法。
如果事实证明以我的方式使用StackPanel
是一种不好的做法,请告诉我原因是什么。
谢谢。
答案 0 :(得分:3)
我建议您在内容演示者内容
上使用DataTemplate<ContentPresenter Content="{Binding X}"/>
<COntentPresenter.ContentTemplate>
<DataTemplate >
<StackPanel>...</StackPanel>
</DataTemplate>
</COntentPresenter.ContentTemplate>
</ContentPresenter/>
您显然需要在StackPanel内部进行一些动态集合处理 - 为了实现它,您可以阅读Bind Collection to StackPanel。这个问题已经回答了。
关于“从dataConverter返回控件是一个好习惯”:
我怀疑。因为转换器不是为了返回完全控制而设计的 - 所以它是datatemplates的任务。嗯,他们显然可以但通常用于更简单的任务 - 一些额外的格式,或颜色的价值。当他们创建控件时,他们违反了MVVM和“XAML中最大的UI”原则。在这种情况下,他们没有任何好处。更糟糕的是 - 代替清晰的XAML和viewmodel逻辑,你很难阅读转换方法。
答案 1 :(得分:0)
以防万一有人想知道真正的答案,除了接受答案中的良好基本答案。
我的新DotMockup类(这是一个定义类的椭圆,以“模拟”真正的椭圆)
public class DotMockup
{
private SolidColorBrush _fill;
private double _diameter;
private Thickness _margin;
public double Diameter
{
get { return _diameter; }
set { _diameter = value; }
}
public SolidColorBrush Fill
{
get { return _fill; }
set { _fill = value; }
}
public Thickness Margin
{
get { return _margin; }
set { _margin = value; }
}
}
我的更新转换器类
public class NoteOctaveConverter : IValueConverter
{
private const double _dotDiameter = 3;
private readonly Thickness _dotMargin = new Thickness(0.25);
private SolidColorBrush _dotFill;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int octave = (int)value;
List<DotMockup> dots = new List<DotMockup>();
if ((octave > 0 && parameter as string == "TOP") || (octave < 0 && parameter as string == "BOT"))
{
_dotFill = Brushes.Black;
}
else
{
_dotFill = Brushes.Transparent;
}
for (int i = 0; i < Math.Abs(octave); i++)
{
dots.Add(new DotMockup());
dots[i].Diameter = _dotDiameter;
dots[i].Margin = _dotMargin;
dots[i].Fill = _dotFill;
}
return dots;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
最后,我的UPDATED XAML部分在StackPanel中显示数据
<ItemsControl Grid.Column="1" Grid.Row="2"
ItemsSource="{Binding Path=MusicalNotation.Octave, Converter={StaticResource NoteOctaveConverter}, ConverterParameter=BOT, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse HorizontalAlignment="Center" VerticalAlignment="Top"
Margin="{Binding Margin}" Fill="{Binding Fill}"
Width="{Binding Diameter}" Height="{Binding Diameter}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
希望这会有所帮助。 (接受的答案不会改变,因为你需要从根本上先了解它)
答案 2 :(得分:0)
解决方案后
非常好。但我的建议很少:
1)根据我的口味,你的转换器仍然太复杂了。理想情况下,您的viewmodel应包含像
这样的DotMockupspublic IEnumerable<DotMockups> OctaveData { get {...}}
你的模型应该包含类似
的内容public Int32 Octave {get {}} // It could be a method if you want
您应该将DotMockups的创建转移到viewmodel层并将八度计算到模型层。
但是,实际上,如果你不想让你的视图模型过于复杂,你可以保持原样。
2)DotMockups成员太像UI了。如果你保持解决方案不是理想但可以通过 - 它们必须只能从你的视图层看到。
但是如果你将它们转移到viewmodel中,它们将成为UI工件 - 所以你必须以UI无关的方式处理它们。在这里,我们已经到了直接应用MVVM可能带来很多问题的那一刻。如果你的对象的表现形式差异很大,那么遵守MVVM会导致大量的代码重复和你自己的画笔,形状等的发明。
但如果我们看看
private const double _dotDiameter = 3;
private readonly Thickness _dotMargin = new Thickness(0.25);
和
if ((octave > 0 && parameter as string == "TOP") || (octave < 0 && parameter as string == "BOT"))
{
_dotFill = Brushes.Black;
}
else
{
_dotFill = Brushes.Transparent;
}
你只有两种类型的点:黑色和透明。所以你可以把它们写成:
公共类DotViewModel { public Boolean IsFilled //我认为在音乐中可能有一些更合适的名字。 { 得到{...}; } }
P.S。:我不会继续下去。首先 - 这篇文章不是问题的答案。第二 - 很抱歉,但StackOverflow不是一些代码审查或代码编写的资源。第三 - 我有一些事要做自己。因此,我将慷慨地让您了解MVVM和WPF的复杂性。我希望我已经展示了一些想法。只需努力,阅读和改进。祝你好运。