0

I have 2 buttons 'Add Title' and 'Add Questions'. They will click 'Add Title' and then a ComboBox and TextBox will appear like this:

enter image description here These objects are stored in a GroupBox as you can see from the method :

C# Code:

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);

}

Then when the user clicks 'Add Question' I need the objects (ComboBox and TextBox) to get added under the title in the same GroupBox.

This is the method for the add question:

C# Code:

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);

    }

This is my attempt on getting the questions objects to appear in the same GroupBox as the Titles. This code returns an error:

enter image description here

EDIT:

This is what I'm trying to achieve.

enter image description here

Sorry if its not explained enough.

Ben Clarke
  • 256
  • 1
  • 6
  • 23

1 Answers1

3

What HighCore and Sean are talking about is Templating. Templating allows for the separation of the UI implementation from the data. In WPF, this Separation of Concerns is usually achieved via the MVVM pattern, where the (M)odel wraps/exposes data entities, the (V)iew renders the models on the screen and the (V)iew(M)odel manipulates the Models.

So, as an example and using the MVVM pattern, I have created 2 models called MyTitleModel and MyQuestionModel. I have also created an interface that they both implement so I can store them both in the same collection in the 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; }
}

The ViewModel manipulates Models and looks like

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; }
}

This is a simple view model. The most complicated part is the RelayCommand which allows me to call directly into the ViewModel from the View.

The view then sets its DataContext to the ViewModel and a template is used to display the model in an items control.

<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>

Note the setting of the DataContext in the Xaml. I could have set this in the code behind as a lot of examples do but but setting the DataContext in the Xaml, visual studio will give me autocomplete on simple data binding paths.

Also note the 2 DataTemplates. I have not set the ItemTemplate of the ItemsControl. By doing this, the ItemsControl will search for an unkeyed DataTemplate with the correct DataType to display each item.

The code behind simply creates and stores a reference to the ViewModel.

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

    public MyViewModel ViewModel { get; set; }
}

The button is hooked up to the RelayCommand so the ViewModel can add a MyModel object to the Items collection.

This is the entire code for my test solution, excluding the RelayCommand which you can get from the answer of this question, so you should be able to easily reproduce it.

Edit

The simple relay command I implemented for this example is

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; }
    }
}

I hope this helps.

Community
  • 1
  • 1
Mark Travis
  • 1,049
  • 9
  • 11
  • I'm going to look into MVVM, I can sort of see what happening. With your first piece of code what is the point of this? Like I am going to store the information in an entity is this what this is used for? I really cant get to grips with the second piece of code though I don't understand what its trying to achieve? I understand the XAML bit of binding the command to the piece of code. – Ben Clarke Jan 02 '14 at 16:04
  • I have updated the answer to match the updated question. – Mark Travis Jan 02 '14 at 16:08
  • The model is used to expose the data entity for use by the UI. For example, the data entity may have a boolean field called IsActive. The Model may use that field and expose it as a property of type Visibility to show or hide the model on the view. The ViewModel is used to manipulate models and to give a coded testing point. Writing automated/unit tests for views is hard but writing a test for a class such as a ViewModel, is very achievable. – Mark Travis Jan 02 '14 at 16:18
  • A couple of other notes about the model The Model is usually a wrapper for the entity and will contain a private copy of the entity. As the model changes, it updates the entity and when the ViewModel is called to save the data, it extracts the updated entities from the models and sends them to be saved. The model implements the INotifyPropertyChanged interface. Multiple models can expose different parts of the same entity to different views. The entity should only worry about its data. – Mark Travis Jan 02 '14 at 16:25
  • Added a question-title relationship to the answer. – Mark Travis Jan 02 '14 at 16:31
  • I'm going to have a quick go at this now, I will definitely let you know how I get on. Thanks for your help with the question. – Ben Clarke Jan 02 '14 at 16:34
  • `ReplayCommand` needs a namespace and I cannot seen to find it anywhere? – Ben Clarke Jan 02 '14 at 16:50
  • It does not need to be a specific namespace. Create a RelayCommand in your projects and just use the name space is supplies. – Mark Travis Jan 02 '14 at 22:47
  • Is there a reference for `DoMyCommand` and `CanDoMyCommand` in the RelayCommand class? Also `AddTitleCommand = new RelayCommand(o => true, o => Items.Add(new MyTitleModel()));` The Lamba statment brings this error `Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement` – Ben Clarke Jan 03 '14 at 09:04