【问题标题】:Adding different objects into a Groupbox将不同的对象添加到 Groupbox
【发布时间】:2014-01-20 01:14:28
【问题描述】:

我有 2 个按钮“添加标题”和“添加问题”。他们将单击“添加标题”,然后一个 ComboBox 和 TextBox 将如下所示:

从方法中可以看出,这些对象存储在 GroupBox 中:

C# 代码:

private void btnAddTitle_Click(object sender, RoutedEventArgs e)
{

        CurrentSortItem++;
        SortItems.Add(CurrentSortItem);

        outerSp =  new StackPanel() { Orientation = Orientation.Vertical };
        sp = new StackPanel() { Orientation = Orientation.Horizontal };
        gp = new GroupBox();

        ComboBox y = new ComboBox();
        y.Name = "Combo" + CurrentSortItem;
        y.SelectedItem = CurrentSortItem;
        y.Height = 25;
        y.Width = 45;
        y.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
        y.Margin = new Thickness(20, 15, 0, 0);

        foreach (int item in SortItems)
        {
            y.Items.Add(item);
        }

        TextBox x = new TextBox();
        x.Name = "Title" + CurrentSortItem;
        x.Text = "Title...";
        x.FontWeight = FontWeights.Bold;
        x.FontStyle = FontStyles.Italic;
        x.TextWrapping = TextWrapping.Wrap;
        x.Height = 25;
        x.Width = 200;
        x.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
        x.Margin = new Thickness(12, 15, 0, 0);

        sp.Children.Add(y);
        sp.Children.Add(x);

        outerSp.Children.Add(sp);
        gp.Content = outerSp;

        spStandard.Children.Add(gp);

}

然后,当用户单击“添加问题”时,我需要将对象(组合框和文本框)添加到同一个 GroupBox 中的标题下。

这是添加问题的方法:

C# 代码:

private void ViewQuestions(StackPanel sp)
{

        var stackPanel = gp.Content as StackPanel;
        if (stackPanel != null)
        {
            stackPanel.Children.Add(sp);
        }
        else
            gp.Content = sp;
}

    List<int> SortItems1 = new List<int>();
    int CurrentSortItem1 = 0;
    int Count = 0;

private void btnQuestion_Click(object sender, RoutedEventArgs e)
{
        outerSp = new StackPanel() { Orientation = Orientation.Vertical };
        sp = new StackPanel() { Orientation = Orientation.Horizontal };

        if (SortItems.Count == 0)
        {
            MessageBox.Show("You must add a title before adding a question", "ERROR", MessageBoxButton.OK, MessageBoxImage.Information);
        }
        else
        {
            Count++;

            CurrentSortItem1++;
            SortItems1.Add(CurrentSortItem1);

                ComboBox y = new ComboBox();
                y.Name = "Combo" + CurrentSortItem1;
                y.SelectedItem = CurrentSortItem1;
                y.Height = 25;
                y.Width = 45;
                y.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
                y.Margin = new Thickness(20, 15, 0, 0);

                foreach (int item in SortItems1)
                {
                    y.Items.Add(item);
                }

                TextBox x = new TextBox();
                x.Name = "Question" + CurrentSortItem1;
                x.Text = "Question...";
                x.FontStyle = FontStyles.Italic;
                x.TextWrapping = TextWrapping.Wrap;
                x.Height = 25;
                x.Width = 500;
                x.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
                x.AcceptsReturn = true;
                x.Margin = new Thickness(100, 15, 0, 0);

                TextBox z = new TextBox();
                z.Name = "Points" + CurrentSortItem;
                z.FontWeight = FontWeights.Bold;
                z.Height = 25;
                z.Width = 45;
                z.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
                z.Margin = new Thickness(250, 15, 0, 0);

                sp.Children.Add(y);
                sp.Children.Add(x);
                sp.Children.Add(z);

                outerSp.Children.Add(sp);

                ViewQuestions(sp);

    }

这是我尝试让问题对象出现在与标题相同的 GroupBox 中。此代码返回错误:

编辑:

这就是我想要实现的目标。

如果解释不够,请见谅。

【问题讨论】:

  • 立即删除所有这些代码。使用ItemsControl。不要在 WPF 的过程代码中创建或操作 UI 元素,这就是 XAML 的用途。
  • 同意。你应该看看编写 C# 的 MVVM 模式:blogs.msdn.com/b/robertgreen/archive/2013/11/14/… 它很好地分离了 UI 和代码隐藏。在这种情况下,这似乎是您所需要的。
  • 此外,要显示/隐藏各种控件,请在视图模型中使用布尔属性,然后通过BooleanToVisibilityConverter 将控件的Visibility 属性绑定到此布尔值。无需即时生成控件。
  • @HighCore 抱歉,我忘了说我是在运行时创建这些的。这在 XAML 上仍然可行吗?
  • @SeanCogan 抱歉,我忘了说我是在运行时创建这些的。这在 XAML 上仍然可行吗?

标签: c# wpf exception


【解决方案1】:

HighCoreSean 谈论的是Templating。模板允许将 UI 实现与数据分离。在 WPF 中,这种关注点分离通常通过 MVVM 模式实现,其中 (M) 模型封装/公开数据实体, (V)iew 在屏幕上渲染模型,(V)iew(M) 模型操作模型。

因此,作为示例并使用 MVVM 模式,我创建了 2 个模型,称为 MyTitleModel 和 MyQuestionModel。我还创建了一个它们都实现的接口,因此我可以将它们存储在 ViewModel 的同一个集合中。

public interface IModel
{
    string Text { get; set; }
}

public class MyQuestionModel : IModel
{
    public MyTitleModel Title { get; set; }
    public string Field1 { get; set; }
    public string Text { get; set; }
}

public class MyTitleModel : IModel
{
    public string Field2 { get; set; }
    public string Text { get; set; }
}

ViewModel 操作模型并且看起来像

public class MyViewModel
{
    public MyViewModel()
    {
        this.Items = new ObservableCollection<IModel>();
        this.Field1Items = new ObservableCollection<string>() { "1", "2", "3" };
        AddTitleCommand = new RelayCommand(o => true, o => Items.Add(new MyTitleModel()));
        AddQuestionCommand = new RelayCommand(o => Items.Any(), o =>
        {
            var title = this.Items.OfType<MyTitleModel>().LastOrDefault();
            Items.Add(new MyQuestionModel() { Title = title });
        });
    }

    public ObservableCollection<IModel> Items { get; set; }

    public ObservableCollection<string> Field1Items { get; set; }

    public RelayCommand AddTitleCommand { get; set; }
    public RelayCommand AddQuestionCommand { get; set; }
}

这是一个简单的视图模型。最复杂的部分是RelayCommand,它允许我直接从 View 调用 ViewModel。

然后视图将其 DataContext 设置为 ViewModel,并使用模板在项目控件中显示模型。

<Window x:Class="StackOverflow._20885502.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:this="clr-namespace:StackOverflow._20885502"
        DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate DataType="{x:Type this:MyTitleModel}">
            <StackPanel Orientation="Horizontal">
                <ComboBox SelectedValue="{Binding Path=Field2}" ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type this:MainWindow}}, Path=ViewModel.Field1Items}" Margin="1 2" />
                <TextBox Text="{Binding Path=Text}" Margin="1 2" Width="200" />
            </StackPanel>
        </DataTemplate>
        <DataTemplate DataType="{x:Type this:MyQuestionModel}">
            <StackPanel Orientation="Horizontal">
                <ComboBox SelectedValue="{Binding Path=Field1}" ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type this:MainWindow}}, Path=ViewModel.Field1Items}" Margin="1 2" />
                <TextBox Text="{Binding Path=Text}" Margin="20 2 1 2" Width="200" />
            </StackPanel>
        </DataTemplate>
    </Window.Resources>
    <DockPanel>
        <StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
            <Button Content="Add Title" Command="{Binding Path=AddTitleCommand}" Width="100" Margin="3" />
            <Button Content="Add Question" Command="{Binding Path=AddQuestionCommand}" Width="100" Margin="3" />
        </StackPanel>
        <ItemsControl ItemsSource="{Binding Path=Items}" />
    </DockPanel>
</Window>

注意 Xaml 中 DataContext 的设置。我可以像很多示例一样在后面的代码中设置它,但是在 Xaml 中设置 DataContext,Visual Studio 将在简单的数据绑定路径上自动完成。

还要注意 2 个数据模板。我没有设置 ItemsControl 的 ItemTemplate。通过这样做,ItemsControl 将搜索具有正确 DataType 的未键入的 DataTemplate 以显示每个项目。

后面的代码只是创建并存储对 ViewModel 的引用。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        ViewModel = new MyViewModel();
        InitializeComponent();
    }

    public MyViewModel ViewModel { get; set; }
}

该按钮已连接到 RelayCommand,因此 ViewModel 可以将 MyModel 对象添加到 Items 集合。

这是我的测试解决方案的全部代码,不包括您可以从question 的答案中获得的 RelayCommand,因此您应该能够轻松地重现它。

编辑

我为这个例子实现的简单中继命令是

public class RelayCommand: ICommand
{
    private Predicate<object> canExecute;

    private Action<object> execute;

    public RelayCommand(Predicate<object> canExecute, Action<object> execute)
    {
        this.canExecute = canExecute;
        this.execute = execute;
    }

    public void Execute(object parameter)
    {
        this.execute.Invoke(parameter);
    }

    public bool CanExecute(object parameter)
    {
        return this.canExecute.Invoke(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }
}

我希望这会有所帮助。

【讨论】:

  • 我将研究 MVVM,我可以看到发生了什么。对于您的第一段代码,这有什么意义?就像我要将信息存储在一个实体中一样,这就是它的用途吗?尽管我不明白它试图实现什么,但我真的无法掌握第二段代码?我理解将命令绑定到代码段的 XAML 位。
  • 我已更新答案以匹配更新后的问题。
  • 模型用于暴露数据实体以供 UI 使用。例如,数据实体可能有一个名为 IsActive 的布尔字段。 Model 可以使用该字段并将其公开为 Visibility 类型的属性,以在视图上显示或隐藏模型。 ViewModel 用于操作模型并提供编码测试点。为视图编写自动化/单元测试很难,但为 ViewModel 之类的类编写测试是非常容易实现的。
  • 关于模型的一些其他说明 模型通常是实体的包装器,将包含实体的私有副本。随着模型的变化,它会更新实体,当调用 ViewModel 保存数据时,它会从模型中提取更新的实体并将它们发送到保存。该模型实现了 INotifyPropertyChanged 接口。多个模型可以将同一实体的不同部分暴露给不同的视图。实体应该只关心它的数据。
  • 为答案添加了问题-标题关系。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-27
  • 1970-01-01
  • 2013-06-23
相关资源
最近更新 更多