Darth Scitus
Darth Scitus

Reputation: 568

Creating a dynamic checkbox list using C#

Im trying to create a list of classes for a degree plan at my university where when classes that have been taken are checked another class gets highlighted to let the user know that class has all of the prerequisites met to be taken. so if i check calculus 1, physics 1 will get highlighted.

Im new to C# and i dont have a heavy knowledge of what the language and .NET framework can do so im asking for a good simple straight answer, if you could explain exactly what is going on in the code that would be fantastic. Thanks

heres what i have so far. just a basic proof of concept WPF

<Window x:Class="degree_plan.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Degree Planner" Height="350" Width="525" Name="Degree">
    <Grid>
        <CheckBox Content="Math 1412" Height="16" HorizontalAlignment="Left" Margin="34,40,0,0" Name="checkBox1" VerticalAlignment="Top" />
        <CheckBox Content="Physics 1911" Height="16" HorizontalAlignment="Left" Margin="34,62,0,0" Name="checkBox2" VerticalAlignment="Top" />
    </Grid>
</Window>

and heres the c# code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation; 
using System.Windows.Shapes;

namespace degree_plan
   {
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
   public partial class MainWindow : Window
   {
      public MainWindow()
      {
        InitializeComponent();

       // if (checkBox1.Checked)
         //   Console.WriteLine("physics 1");

    }
  }
}

Upvotes: 0

Views: 6047

Answers (3)

Andy
Andy

Reputation: 6466

I know you asked for simple but at some point you can come back to this as it's a very structured and expandable way to hold and use data in WPF

I would consider quantifying the Classes in their own structure, each with a list of the prerequisite classes that must be completed before hand, I would like to suggest using the following to achieve what you're after (sorry, bit long!)

what you'll get is a list of classes represented by checkboxes, you can only check a class once all of its prerequisite classes are complete, they have names and descriptions and can be customised on the UI in anyway you want.

Create a new WPF application and add the following Class. Class.cs

public class Class : Control, INotifyPropertyChanged
{
    // Property that's raised to let other clases know when a property has changed.
    public event PropertyChangedEventHandler PropertyChanged;

    // Flags to show what's going on with this class.
    bool isClassComplete;
    bool isPreComplete;

    // Some other info about the class.
    public string ClassName { get; set; }
    public string Description { get; set; }

    // A list of prerequisite classes to this one.
    List<Class> prerequisites;

    // public property access to the complete class, you can only set it to true
    // if the prerequisite classes are all complete.
    public bool IsClassComplete
    {
        get { return isClassComplete; }
        set
        {
            if (isPreComplete)
                isClassComplete = value;
            else
                if (value)
                    throw new Exception("Class can't be complete, pre isn't complete");
                else
                    isClassComplete = value;

            PropertyChangedEventHandler temp = PropertyChanged;
            if (temp != null)
                temp(this, new PropertyChangedEventArgs("IsClassComplete"));
        }
    }

    // public readonly property access to the complete flag.
    public bool IsPreComplete { get { return isPreComplete; } }

    public Class()
    {
        prerequisites = new List<Class>();
        isPreComplete = true;
    }

    // adds a class to the prerequisites list.
    public void AddPre(Class preClass)
    {
        prerequisites.Add(preClass);
        preClass.PropertyChanged += new PropertyChangedEventHandler(preClass_PropertyChanged);
        ValidatePre();
    }

    // removes a class from the prerequisites lists.
    public void RemovePre(Class preClass)
    {
        prerequisites.Remove(preClass);
        preClass.PropertyChanged -= new PropertyChangedEventHandler(preClass_PropertyChanged);
        ValidatePre();
    }

    // each time a property changes on one of the prerequisite classes this is run.
    void preClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case "IsClassComplete":

                // check to see if all prerequisite classes are complete/
                ValidatePre();
                break;
        }
    }

    void ValidatePre()
    {
        if (prerequisites.Count > 0)
        {
            bool prerequisitesComplete = true;
            for (int i = 0; i < prerequisites.Count; i++)
                prerequisitesComplete &= prerequisites[i].isClassComplete;
            isPreComplete = prerequisitesComplete;
            if (!isPreComplete)
                IsClassComplete = false;
        }
        else
            isPreComplete = true;

        PropertyChangedEventHandler temp = PropertyChanged;
        if (temp != null)
            temp(this, new PropertyChangedEventArgs("IsPreComplete"));
    }
}

Now in the code behind for MainWindow.cs you can create a collection of classes, I've done this in the constructor and provided an observable collection of classes so when new classes are added, you don't have to do anything to get them to display on the UI

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public ObservableCollection<Class> Classes
    {
        get { return (ObservableCollection<Class>)GetValue(ClassesProperty); }
        set { SetValue(ClassesProperty, value); }
    }
    public static readonly DependencyProperty ClassesProperty = DependencyProperty.Register("Classes", typeof(ObservableCollection<Class>), typeof(MainWindow), new UIPropertyMetadata(null));

    public MainWindow()
    {
        InitializeComponent();

        Class math = new Class()
        {
            ClassName = "Math 1412",
            Description = ""
        };

        Class physics = new Class()
        {
            ClassName = "Physics 1911",
            Description = "Everything everywhere anywhen",
        };
        physics.AddPre(math);
        Classes = new ObservableCollection<Class>();
        Classes.Add(math);
        Classes.Add(physics);
    }
}

The final step is to tell WPF what a class is supposed to look like on the user interface, this is done in resources, to simplify the example, I've put it in the MainWindow.xaml file.

<Window x:Class="WpfApplication8.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication8"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <!-- This tells WPF what a class looks like -->
        <Style TargetType="{x:Type local:Class}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:Class}">
                        <StackPanel Orientation="Horizontal" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
                            <!-- Checkbox and a text label. -->
                            <CheckBox  IsEnabled="{Binding IsPreComplete}" IsChecked="{Binding IsClassComplete}" />
                            <TextBlock Margin="5,0,0,0" Text="{Binding ClassName}" ToolTip="{Binding Description}"/>
                        </StackPanel>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>


    <Grid>
        <!-- This draws the list of classes from the property in MainWindow.cs-->
        <ItemsControl ItemsSource="{Binding Classes}"/>
    </Grid>
</Window>

Upvotes: 1

Tirumudi
Tirumudi

Reputation: 443

For your ease, try to use the 'CheckedChanged' event.. Just double click the CheckBox on the designer. Handler will be added automatically(something like,

    private void checkBox1_CheckedChanged(object sender, EventArgs e)
    {

    }

.Then add your code there. But, this is time consuming(since, you have to add handler for each CheckBox). But, 'll be easy for you to understand at this stage.

Upvotes: 0

Carson Myers
Carson Myers

Reputation: 38564

You can register an event handler for the checkboxes:

AddHandler(CheckBox.CheckedEvent, new RoutedEventHandler(CheckBox_Click));

then, create the event handler:

private void CheckBox_Click(object sender, RoutedEventArgs e)
{
    CheckBox checkbox = e.Source as CheckBox
    //...
}

The checkbox variable in the event handler is the checkbox which was clicked to raise the event. You can check which checkbox it was, and then enable all the options which depend on it.

Upvotes: 1

Related Questions