在转换器中初始化StackPanel并使用ContentPresenter呈现它是一个很好的MVVM实践吗?

时间:2014-06-20 16:00:01

标签: c# wpf mvvm ivalueconverter

所以这是我上次问题How to Vertically “center” align the multi line text

中的一些跟进问题

最后一次是来自回答者的评论:

  

正是这样,加上你不应该操纵代码中的UI元素,而是让自己熟悉MVVM

回答者也使用EllipseGeometryPath很好地回答了问题。但是,因为我对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?

*)这样=使用StackPanelConverter展示ContentPresenter

附加说明:

我使用ContentPresenter方式而不是Path的原因是我需要很好地理解我的工作,因为我可能必须在演示会上将它呈现给我的讲师(我不是想要呈现一些新的(有风险的), IF 我有另一种可能对我来说更舒服的方法。

如果事实证明以我的方式使用StackPanel是一种不好的做法,请告诉我原因是什么。

谢谢。

3 个答案:

答案 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应包含像

这样的DotMockups
public 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的复杂性。我希望我已经展示了一些想法。只需努力,阅读和改进。祝你好运。