WPF - Force Binding on Invisible ComboBox - c#

I have a WPF window that contains multiple user controls, some of which are invisible (Visibility = Hidden). One of these controls has a ComboBox that has an ItemsSource binding, and I want to preset its selected item while the window/control is loading.
However, it seems like the binding is not applied until the combobox is visible. When I go to set the SelectedItem property and I hit a breakpoint in the debugger, I notice that ItemsSource is null at that moment. Is there a way to force WPF to apply the data binding and populate the combobox while it stays invisible?
Reproducible Example:
MainWindow.xaml
<Window x:Class="HiddenComboBoxBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:HiddenComboBoxBinding"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Border x:Name="comboboxParent" Visibility="Collapsed">
<ComboBox x:Name="cmbSearchType" SelectedIndex="0" ItemsSource="{Binding SearchTypeOptions}" DisplayMemberPath="Name" SelectionChanged="cmbSearchType_SelectionChanged" />
</Border>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace HiddenComboBoxBinding
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ViewModel viewModel { get; set; } = new ViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = viewModel;
// Add some or all of our search types - in the real code, there's some business logic here
foreach (var searchType in SearchType.AllSearchTypes)
{
viewModel.SearchTypeOptions.Add(searchType);
}
// Pre-select the last option, which should be "Bar"
cmbSearchType.SelectedItem = SearchType.AllSearchTypes.Last();
}
private void cmbSearchType_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
}
ViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HiddenComboBoxBinding
{
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<SearchType> SearchTypeOptions { get; set; } = new ObservableCollection<SearchType>();
#region INotifyPropertyChanged Members
private void NotifyPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class SearchType
{
// Source list of Search Types
private static List<SearchType> _AllSearchTypes;
public static List<SearchType> AllSearchTypes
{
get
{
if(_AllSearchTypes == null)
{
_AllSearchTypes = new List<SearchType>();
_AllSearchTypes.Add(new SearchType() { Name = "Foo" });
_AllSearchTypes.Add(new SearchType() { Name = "Bar" });
}
return _AllSearchTypes;
}
}
// Instance properties - for the purposes of a minimal, complete, verifiable example, just one property
public string Name { get; set; }
}
}

I was able to figure out the issue. Setting the SelectedItem actually did work (even though ItemsSource was null at that time) but in the XAML, the ComboBox had SelectedIndex="0" and it was taking precedence over the SelectedItem being set in the code-behind.

Related

C# wpf bind tabcontrol based on listitem selection

I want to change a complete TabControl based on the ListItem selection. So, I generate a Dictionary with Key(list item) and Value(TabControl) in a loop. Each TabControl contains 10 TabItems.
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ListboxContainer listBoxContainer = new ListboxContainer();
TabControlContainer tabControlContainer = new TabControlContainer();
Dictionary<string, TabControl> tabControlDict = new Dictionary<string, TabControl>();
public MainWindow()
{
InitializeComponent();
listBoxContainer.ListBoxItems = new string[] { "TabControl1", "TabControl2", "TabControl3" };
DataContext = listBoxContainer;
// Generate tabcontrols per listbox item
foreach (var key in listBoxContainer.ListBoxItems)
{
TabControl tabControl = new TabControl();
TabItem tabItem = new TabItem();
for (int i = 0; i < 10; i++)
{
tabItem.Header = $"Header: {i}";
}
tabControl.Items.Add(tabItem);
tabControlDict.Add(key, tabControl);
}
tabControlContainer.TabControls = tabControlDict.Values.ToArray();
}
}
public class TabControlContainer : INotifyPropertyChanged
{
private TabControl[] _tabControls;
public TabControl[] TabControls
{
get => _tabControls;
set
{
if (value != _tabControls)
{
_tabControls = value;
NotifyPropertyChanged("TabControls");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ListboxContainer : INotifyPropertyChanged
{
private string[] _listBoxitems;
public string[] ListBoxItems
{
get => _listBoxitems;
set
{
if (value != _listBoxitems)
{
_listBoxitems = value;
NotifyPropertyChanged("ListBoxItems");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I tried to bind the stackpanel to the tabcontrols, but I can't get it to work.
Here is the MainWindow.xaml code.
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<DockPanel>
<ListBox x:Name="Listbox" ItemsSource="{Binding ListBoxItems}"/>
<StackPanel x:Name="TabControlContainer">
<ItemsControl ItemsSource="{Binding TabControls}">
<!--<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>-->
</ItemsControl>
</StackPanel>
</DockPanel>
</Window>
So, basically, I want to achieve a Master/Detail view, where Master is the listbox and Detail is its belonding TabControl.
I know, that I can manually add/delete children of the Stackpanel, based on list item selection, but it seems to me not the MVVM way.
You want a ListBox and a TabControl.
Both controls you have there are itemscontrols.
They both inherit from Selector so they have SelectedItem.
Bind a collection of viewmodels to your listbox, call these something meaningful. PersonViewModel or FruitViewModel or something. Don't call them listboxitem or anything that is the same name or anything like the name of a UI control. One per row. But let's call them RowVM as in a viewmodel holding the data for a listbox row.
If you bind to SelectedItem of that ListBox you get an instance of RowVM.
You can bind that from your listbox to a property in your WindowViewModel. You can also bind to that property from some other control.
Let's go back to meaningful names though.
Maybe a Person has Shoes. A collection of Shoe that you're going to show in that tabcontrol. Choose a Person in the listbox and you see their shoes in the tabcontrol. So PersonViewModel would have an observablecollection of shoe.
You can bind itemssource of that tabcontrol to Shoes of the selected item of the listbox. Which will then grab that collection of shoes for whichever person you choose in the listbox.
That'll be quite a clunky sort of a binding though and binding both to a property in your viewmodel is usually cleaner.
If you bind that listbox to a collectionview, you can alternatively use the current item / notation in your binding.
https://social.technet.microsoft.com/wiki/contents/articles/29859.wpf-tips-bind-to-current-item-of-collection.aspx
And of course that'll just give you the type name because it doesn't know how to display a shoe.
But that's no problem because wpf has a whole data templating system. That allows you to give it a template to use for a shoe.
You should be able to google examples of all that stuff but here's one on the tabcontrol specifically:
How do I bind a TabControl to a collection of ViewModels?
I hope that's enough to point you in the right direction. Or at least along a path with less rocks.

ArgumentException while reordering listViewItems

I'm trying to reorder listView items with a mouse and I'm getting this mysterious Parameter is not valid ArgumentException either when I'm starting to drag or when I'm dropping item. There are no other details, no stack trace. Crashes entire app.
It works fine when I'm reordering ObservableCollection<string> but keeps crashing on ObservableCollection<MyControl>
MyControl is just a simple UserControl with a TextBlock inside.
I tried CollectionViewSource approach and it's the same.
Any ideas?
MainPage.xaml
<Page
x:Class="App3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<ListView ItemsSource="{Binding Items}" CanReorderItems="True" AllowDrop="True"/>
</Page>
MainPage.xaml.cs
using Windows.UI.Xaml.Controls;
namespace App3
{
public sealed partial class MainPage : Page
{
private MainPageViewModel mainPageViewModel;
public MainPage()
{
this.InitializeComponent();
mainPageViewModel = new MainPageViewModel();
DataContext = mainPageViewModel;
// THIS IS NOT WORKING
MyControl myControl1 = new MyControl("Hello1");
MyControl myControl2 = new MyControl("Hello2");
MyControl myControl3 = new MyControl("Hello3");
mainPageViewModel.Items.Add(myControl1);
mainPageViewModel.Items.Add(myControl2);
mainPageViewModel.Items.Add(myControl3);
// THIS IS WORKING
//string s1 = "h1";
//string s2 = "h2";
//string s3 = "h3";
//mainPageViewModel.Items.Add(s1);
//mainPageViewModel.Items.Add(s2);
//mainPageViewModel.Items.Add(s3);
}
}
}
MainPageViewModel.cs
using System.Collections.ObjectModel;
namespace App3
{
public class MainPageViewModel : BaseModel
{
// THIS IS NOT WORKING
private ObservableCollection<MyControl> items = new ObservableCollection<MyControl>();
public ObservableCollection<MyControl> Items
{
get { return items; }
set
{
items = value;
OnPropertyChanged();
}
}
// THIS IS WORKING
//private ObservableCollection<string> items = new ObservableCollection<string>();
//public ObservableCollection<string> Items
//{
// get { return items; }
// set
// {
// items = value;
// OnPropertyChanged();
// }
//}
}
}
MyControl.xaml
<UserControl
x:Class="App3.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<TextBlock Name="tb"/>
</UserControl>
MyControl.cs
using Windows.UI.Xaml.Controls;
namespace App3
{
public sealed partial class MyControl : UserControl
{
public MyControl(string sName)
{
this.InitializeComponent();
tb.Text = sName;
}
}
}
BaseModel.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace App3
{
public class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName]string name = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
EDIT
Looks like there's also no problem with reordering simple objects, so I guess there is something wrong with reordering UserControl.
namespace App3
{
public class SampleClass
{
private string sName;
public SampleClass(string sName)
{
this.sName = sName;
}
public override string ToString()
{
return sName;
}
}
}
EDIT 2
Reordering objects that derive from CheckBox crashes exactly like MyControl.
using Windows.UI.Xaml.Controls;
namespace App3
{
public class MyCheckBox : CheckBox
{
public MyCheckBox(string sName)
{
Content = sName;
}
}
}
Same for standard checkBoxes
CheckBox checkBox1 = new CheckBox() { Content = "hello1" };
CheckBox checkBox2 = new CheckBox() { Content = "hello2" };
CheckBox checkBox3 = new CheckBox() { Content = "hello3" };
mainPageViewModel.Items.Add(checkBox1);
mainPageViewModel.Items.Add(checkBox2);
mainPageViewModel.Items.Add(checkBox3);
EDIT 3
Seems like the only solution is to use simple class for holding data
using Windows.UI.Xaml.Controls;
namespace App3
{
public class MyCheckBox : BaseModel
{
private string sName;
public string Name
{
get { return sName; }
set
{
sName = value;
OnPropertyChanged();
}
}
public MyCheckBox(string sName)
{
Name = sName;
}
}
}
and DataTemplate for ListViewItem to display it
<Page
x:Class="App3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<ListView ItemsSource="{Binding Items}" CanReorderItems="True" AllowDrop="True">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Page>
instead of adding UserControl directly to ListView items.
It looks like the reordering of Visuals is not supported entirely. When reorganizing items in a listview, a lot happens underneath, as for example dynamically creating and destroying containers that hold your content, etc.
In order to display the resorted element, some items need to be appended elsewhere in the visual tree.
The drag/drop tries to attach an already attached Visual (your UserControl) which might cause the exception - a Visual can not be attached to multiple parents.
Your approach of using POCOs / ViewModels with Datatemplates should be sufficient to get the work done, as the de- and attach semantics are considered by ListView already.
There should be more exception details, though.

ComboBox does not update when ItemsSource changes

I hav a ComboBox that is bound to a List<string>. When the List changes, the ComboBox does not, even though PropertyChanged was raised. When debugging, I found out that the List Property is even read.
The error can be reproduced using the following code:
XAML
<Window x:Class="ComboBoxTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="90" Width="400">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ComboBox ItemsSource="{Binding Source, Mode=OneWay}"/>
<Button Grid.Column="1" Content="add string" Command="{Binding}" CommandParameter="Add"/>
</Grid>
</Window>
Code behind
using System.Windows;
namespace ComboBoxTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
}
ViewModel
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
namespace ComboBoxTest
{
class ViewModel : INotifyPropertyChanged, ICommand
{
public ViewModel()
{
Source = new List<string>();
Source.Add("Test1");
Source.Add("Test2");
Source.Add("Test3");
}
private List<string> _Source;
public List<string> Source
{
get { return _Source; }
set
{
_Source = value;
OnPropertyChanged("Source");
}
}
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public bool CanExecute(object parameter)
{
return true;
}
public event System.EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if ((string)parameter == "Add")
{
Source.Add("New string");
OnPropertyChanged("Source");
}
}
}
}
Why isn't the ComboBox updating?
The ComboBox does not update because it doesn't see any changes when it checks the List. The reference stays the same and the ComboBox is not informed about changes inside the List.
Refactoring the code to use ObservableCollection instead of List will solve the problem, because ObservableCollection implements INotifyCollectionChanged, what is necessary to inform the View about Changes inside an Object.

WPF MVVM ContextMenu binding IsOpen to Model

I have a button with a context menu associated to it. I can right click on the button and show the context menu as you would expect, however I want to be able to show the context menu after another event, such as a left click, or a drag and drop style event.
I am attempting to do this by binding the IsOpen property of the context menu to the view model, but this is not working as expected. On first left click of the button, nothing happens, although I can see the property on the view model that IsOpen is bound to being updated correctly.
If I right click, the menu will display correctly, and after this if I left click the menu will also show.
Has anyone ever seen this or have any ideas on what I need to do to get the contextMenu to open when the IsOpen property is updated?
XAML
<Window x:Class="PopUpTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mvp="clr-namespace:PopUpTest"
Title="MainWindow" Height="350" Width="525" x:Name="This">
<Window.DataContext>
<mvp:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.Resources>
<ContextMenu x:Key="Menu" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}" IsOpen="{Binding PopupViewModel.IsOpen, Mode=TwoWay}">
<MenuItem Header="Delete" />
</ContextMenu>
</Grid.Resources>
<Button Command="{Binding DisplayPopupCommand}" ContextMenu="{StaticResource Menu}" Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}"/>
</Grid>
Code Behind
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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;
using Microsoft.Practices.Prism.Commands;
namespace PopUpTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class MainWindowViewModel : BaseViewModel
{
private readonly PopupViewModel<ChildViewModel> _popupViewModel;
private readonly DelegateCommand _displayPopupCommand;
public MainWindowViewModel()
{
_popupViewModel = new PopupViewModel<ChildViewModel>(new ChildViewModel { FirstName = "John", LastName = "Doe" });
_displayPopupCommand = new DelegateCommand(() => { PopupViewModel.IsOpen = PopupViewModel.IsOpen == false; Console.WriteLine(PopupViewModel.IsOpen); });
}
public ICommand DisplayPopupCommand
{
get { return _displayPopupCommand; }
}
public PopupViewModel<ChildViewModel> PopupViewModel
{
get { return _popupViewModel; }
}
}
public class PopupViewModel<T> : BaseViewModel
{
private readonly T _data;
public PopupViewModel(T data)
{
_data = data;
}
public T Data
{
get { return _data; }
}
private bool _isOpen;
public bool IsOpen
{
get { return _isOpen; }
set
{
if (_isOpen != value)
{
_isOpen = value;
OnPropertyChanged("IsOpen");
}
}
}
}
public class ChildViewModel : BaseViewModel
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
if (_firstName != value)
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
if (_lastName != value)
{
_lastName = value;
OnPropertyChanged("LastName");
}
}
}
}
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I have been able to solve this by introducing a BindingProxy to the XAML as described in the answer to this post on the MSDN forums:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/a4149979-6fcf-4240-a172-66122225d7bc/wpf-mvvm-contextmenu-binding-isopen-to-view-model?forum=wpf
The binding proxy gets around the issue where the ContextMenu does not have a DataContext until it first displays after a right click.
The issue is discussed further here:
http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/

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");
}
}
}
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.
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>
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.

Categories