A Tale Of Three Widgets
A Software How-To

In this article we are going to touch on Observable Collections, Data Binding, Complex Automation, WPF, and Third Party controls. For this program I created three custom widgets, each with some sort of measurement unit attached to it as well as other properties and methods. The first Widget has three Lengths as properties and the user needs the units to be in inches. Well easy enough, slap a text tag "Inche(s)" after the values the user enters and done. Well, not really. What if the user needs to see what the values are in Metric or what if the person entering the values only has the values in feet, and is very bad at multiplication...

Now we have a challenge, how to assign a unit of measurement to something that will automatically update when the user switches measurement units, like inches to feet; or even toggling between English (US) and Metric (SI) measurement systems.

Quake USA Measurement System Unit Manager

In the following screen shot we have updated Widget One's State Of Mind and Color which are both reflected in the Widget One TreeView Item. The Length units have also been changed from the default inches - "in (US)" to feet - "ft (US)".

Quake USA Measurement System Unit Manager

Length will be the first measurement unit to incorporate into our application. For Length, inches, feet - for English units; centimeters, millimeters, and meters will be supported for Metric units. We need an object, a container for our units. We will call this container a Bucket. The Bucket will hold any type of measurement that we need, it will become our class to deal with the Measurement Systems. Looking forward we also want to support Area with square inches, centimeters, and millimeters as values; and Volume with gallons, liters, cubic - feet, meters, centimeters, and millimeters.

In order to support changing units, we need to maintain a "control" value. The control value will be stored in the default units - for Length that would be inches. Using the control value, a display value of any unit is created and maintained. We will call the control value the "RealBucket" and the value the user sees in the selected units is the "DisplayBucket". So we know we need two values for Length - LengthRealBucket and LengthDisplayBucket, same for Volume and Area - VolumeRealBucket, VolumeDisplayBucket, AreaRealBucket, and finally AreaDisplayBucket.

There is a relationship between the units of measurement. For example, the default Length unit will be inches; from this we know that there are .08333 inches per foot, 2.54 centimeters per inch, 25.4 millimters per inch and so on. From this we can define our Bucket as a formulaic relation to one another. The Bucket will have the following properties to encapsulate a measurement unit:

  • Unit it represents - Length, Area, Volume
  • Measurement system of the Bucket - (US or SI)
  • Conversion value - numeric conversion from the default unit - (Inches = 1.00, Feet = .08333)
  • Shift value if needed that will through addition or subtraction will adjust the overall quantity

An example of a measurement unit that would need the shift would be Temperature. Degrees Celcius would be defined as new Bucket("Celcius", "Degrees Celcius", 1.0, 0.0, BucketType.Temperature) and Fahrenheits relation to Celcius is Temp(celcius) * 1.8 + 32. So the formula would be as follows: new Bucket("Fahrenheit", "Degrees Fahrenheit", 1.8, 32.0, BucketType.Temperature).

So our measurement units class is taking shape. Below is the definition for Length Units. Area and Volume are very similar, they can be viewed in the source. Note however; that any type and any number of measurement types can be added - such as Mass, Velocity, Temperature, etc...


// Define Length
LengthBuckets = new[]
{
  new Bucket("in", "US", 1.0, 0.0, BucketType.Length),
  new Bucket("ft", "US", 0.0833333, 0.0, BucketType.Length),
  new Bucket("cm", "SI", 2.54, 0.0, BucketType.Length),
  new Bucket("mm", "SI", 25.4, 0.0, BucketType.Length),
  new Bucket("m", "SI", 0.0254, 0.0, BucketType.Length)
};
// Set to default
LengthDisplayBucket = LengthBuckets[0];
LengthRealBucket = LengthBuckets[0];

Note the Bucket Type, since we have Area, Length, and Volume we need to know which bucket we are dealing with. Definition of our Buckets is only one step, now we have to convert these when the user changes them so we need an event mechanism - the Property Changed Event Handler. Each unit property will have a name associated with it, for Length we assigned the property names of "LengthRealBucket" and "LengthDisplayBucket". The name of the property gets pushed via the property changed event:


/// <summary>
///  Event handler to notify when a property has changed and passes the changed property up the stack
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
internal void Changed(string id)
{
     PropertyChangedEventHandler eventhandler = PropertyChanged;
     if (null != eventhandler) 
     	eventhandler(this, new PropertyChangedEventArgs(id));
}

Using the Bucket definition we can create a calculate routine that will handle conversion between compatible types of units. In other words, the routine can switch between all length units, all volume units, and all area units by using the conversion values we associated with each Bucket.


internal static double 
Calculate(Bucket bucket_before, Bucket bucket_after, double value)
{
     if (bucket_before.BType != bucket_after.BType) throw 
     	new InvalidOperationException("Conversion Between Incorrect Types!");
     if ((bucket_before.BucketShift == bucket_after.BucketShift) && 
     		(bucket_before.BucketScale == bucket_after.BucketScale)) return value;
     double retVal = (value - bucket_before.BucketShift) / bucket_before.BucketScale;
     return (retVal * bucket_after.BucketScale + bucket_after.BucketShift);
}

Two routines are needed that will call the Calculate method. The first is to calculate the display bucket from the real bucket as shown here. Note that the other units of Area and Volume have been included and is controlled by the Bucket Type. Don't worry about copying these code fragments, just follow along. I'll make the code for the complete classes available at the end of the article. I can't give out the whole source since I'm using the Property Grid from the Xceed Library. It's a license issue...


internal double 
CalcFromRealBucket(BucketType btype, double value) 
{
    switch(btype)
    {
        case BucketType.Area:
            return Calculate(AreaRealBucket, AreaDisplayBucket, value);
        case BucketType.Length:
            return Calculate(LengthRealBucket, LengthDisplayBucket, value);
        case BucketType.Volume:
            return Calculate(VolumeRealBucket, VolumeDisplayBucket, value);
        default:
            throw new 
            InvalidOperationException("Error!  Bucket Types Supported Are Area, Length, And Volume");
    }  // End switch
} // End CalcFromRealBucket()

A quick tidbit on WPF before we look at the actual Widget that will use the Units class. As you can see from the screen shot WPF is really great for making the UI look like something more than the plain old app. Even applying a linear gradient to the combo boxes makes them look like something special, something different from the same old plain combo box. Yeah, it is great, until the user puts the focus on the control - then YUK!!

If you do any customization of the GUI, then be prepared for having to deal with GotFocus / LostFocus events to force things to look pretty. In the case of the button and the window background I used a LinearGradient to define the color scheme. I won't go into how to use a LinearGradient since there is a lot on the web about it. The GotFocus event is used to override the highlight colors by defining a new foreground and background. We also capture the LinearGradient of the current background before switching. Define a public variable to hold the LinearGradient background. Then in the GotFocus event we save the LinearGradient background before setting it to Gold with a foreground of black for visibility.


// Combo box Linear Gradient variable to store the current Background
public static LinearGradientBrush lgb_ComboBackground;
.
.
.
/// <summary>
/// Next two routines handle the glamour for the Bucket System Combo Box
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void 
cmbBucketSystem_GotFocus(object sender, RoutedEventArgs e)
{
	// Store the current glamour to make it easy to restore at lostfocus()
    lgb_ComboBackground = (LinearGradientBrush) cmbBucketSystem.Background;     
    cmbBucketSystem.Background = Brushes.Gold;
    cmbBucketSystem.Foreground = Brushes.Black;
}
private void cmbBucketSystem_LostFocus
	(object sender, RoutedEventArgs e)
{
    cmbBucketSystem.Foreground = Brushes.Gold;
    cmbBucketSystem.Background = lgb_ComboBackground;
}

The Measurement Unit class Buckets is done, now it needs to be utilized. A custom Widget will be created that will need Length measurements. The Widget will contain three lengths associated with it, why three - just decided at random to have three different lengths. Each will update when the units or the measurement system changes. Below are the protected property variables that Widget One will have. The Properties are Name, Color, State Of Mind, Length A, Length B, Length C, and Date Created.

There are two other Widgets, one has Area as a measurement unit and the other uses Volume. The set up for each is very similar to Widget One, but I included them in the code to show some more complexity and versatility as well.


// Declare the vars needed - These are the properties of Widget One
// Widget Name
private string _widget_name = string.Empty;          
// Widget Color
private string _widget_color = string.Empty;         
// Widget State of mind... ;)
private string _widget_state = string.Empty;         
// Store the date and time the widget was created
private DateTime _widget_created = DateTime.Now;     
// Length A
private double _widget_length_A = 0.0;               
// Length B
private double _widget_length_B = 0.0;               
// Length C
private double _widget_length_C = 0.0;               
// Stores the current unit of measurement
private string _widget_length_bucket = string.Empty; 

For each length the Widget will need to maintain two values as discussed earlier.

        
// Length A of Widget One - Control value
public double RealLengthA
{
    get { return _widget_length_A; }
    set
    {
        if (value != _widget_length_A)
        {
            _widget_length_A = value;
            PropChanged("RealLengthA");
            PropChanged("DisplayLengthA");
        }
    }
}

// Widget Length A
public double DisplayLengthA
{
    get { return 
    	Buckets.Instance.CalcFromRealBucket(BucketType.Length, _widget_length_A); }
    set
    {
        double len_val = Buckets.Instance.CalcToRealBucket(BucketType.Length, value);
        if (len_val != _widget_length_A)
        {
            _widget_length_A = len_val;
            PropChanged("RealLengthA");
            PropChanged("DisplayLengthA");
        }
     }
}

For each, real and display; if the value changes we want both to be in sync so we raise the Property Changed on both values: "RealLengthA" and "DisplayLengthA". We need to attach the PropertyChanged event to the Widget as well as the routine that will handle the event. The Buckets class will take care of knowing how to update the values via routines just mentioned Buckets.Instance.CalcFromRealBucket and Buckets.Instance.CalcFromDisplayBucket.

    
/// <summary>
/// Widget One definition
/// </summary>
public class widgetOne
{
    public widgetOne()
    {
        Buckets.Instance.PropertyChanged += BucketsChanged;
        EnumBinding = SystemType.UnitedStates;
    }
    void BucketsChanged(object sender, PropertyChangedEventArgs e)
    {
        PropChanged("RealLengthA");
        PropChanged("DisplayLengthA");
        PropChanged("RealLengthB");
        PropChanged("DisplayLengthB");
        PropChanged("RealLengthC");
        PropChanged("DisplayLengthC");
        PropChanged("LengthBuckets");
    }
.
.
.

Now the code for PropChanged which declares the Property Changed Event Handler and passes the changed Property Name to the handler. The other portion needed for this mechanism is the Enumeration Binding which assigns the correct units when the system of measurement changes from English to Metric and vice versa. It will call the support routine TypeChanged to assign the correct default value.

        
/// <summary>
/// Controls switching between measurement systems
/// </summary>
private SystemType _enumBinding;

public SystemType EnumBinding
{
    get
    {
        return _enumBinding;
    }
    set
    {
        if (_enumBinding != value)
        {
            if (value == SystemType.UnitedStates)
            {
                _enumBinding = SystemType.UnitedStates;
                TypeChanged();
            }
            else
            {
                _enumBinding = SystemType.InternationalSystem;
                TypeChanged();
            }
            if (PropertyChanged != null)
                    PropertyChanged(this, new 
                    PropertyChangedEventArgs("EnumBinding"));
        }
        else
        { 
            if (value == SystemType.UnitedStates)
            {
                //_enumBinding = SystemType.InternationalSystem;
                _enumBinding = SystemType.UnitedStates;
                TypeChanged();
            }
            else
            {
                //_enumBinding = SystemType.UnitedStates;
                _enumBinding = SystemType.InternationalSystem;
                TypeChanged();
            }
            if (PropertyChanged != null)
                PropertyChanged(this, new 
                PropertyChangedEventArgs("EnumBinding"));

        }
    }
}  // End EnumBinding

/// <summary>
/// Handles switching between measurement systems United States and International
/// </summary>
public void TypeChanged()
{
	// United States System Of Measurement
    if (EnumBinding.Equals(SystemType.UnitedStates))        
    {
		// Set to Inches
        Buckets.Instance.LengthDisplayBucket = Buckets.Instance.LengthBuckets[0];  
    }
	// International System Of Measurement
    else     
    {
		// Set to Centimeters
        Buckets.Instance.LengthDisplayBucket = Buckets.Instance.LengthBuckets[2];  
    }
}

Now that we have all the code we need for Widget One, be sure to add using System.Collections.ObjectModel so that we can use the ObservableCollection to define our list of Widget Ones(s).


/// <summary>
/// Define the Observable Collection for Widget One
/// </summary>
public class widgetOneList : ObservableCollection<widgetOne>
{
    public widgetOneList()
        : base()
    { }
}

In the main program we can utilize our new Widget.


// Observable collection list for all Widget One creations
public static widgetOneList list_WidgetOne = new widgetOneList();   

The following routine creates a new Widget and adds it to the TreeView. In the XAML we define a Data Template for Widget One so the Treeview knows how we want the widget to be displayed as well as what data to use. A label will be used for the name, text boxes for Color and State of Mind as well as a button. The button will be for the user to select the Widget from the list of Widgets in the TreeView. Once selected the Xceed PropertyGrid will be assigned the data context of the selected Widget. The Data Template will be defined under the TreeView.Resources tag.


/// <summary>
/// Creates a new Widget One
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CreateOne_Click(object sender, RoutedEventArgs e)
{
    string str_NewWidgetName = "Widget One - " + list_WidgetOne.Count.ToString();
    var newWidget = new widgetOne();
    newWidget.WidgetName = str_NewWidgetName;
    newWidget.WidgetState = "Happy";    
    	// Set to default
    newWidget.WidgetColor = "Black";    
    	// Set to default
    newWidget.EnumBinding = (SystemType) systype_ProgramMeasurement;
    list_WidgetOne.Add(newWidget);

    UpdateTreeView();
    LblStatus.Content = "New Widget One Created: " + newWidget.WidgetCreated;
}

<!--Widget One Data Template-->
<DataTemplate DataType="{x:Type local:widgetOne}">
    <StackPanel Orientation="Vertical" 
    Margin="1,1,1,1" MinWidth="200" 
    Width="Auto" MinHeight="90" 
    Height="Auto">
        <Border BorderBrush="Black" 
        BorderThickness="1" 
        Padding="2" Margin="2">
        <Grid>
            <Grid.RowDefinitions>
              <RowDefinition Height="30" />
              <RowDefinition Height="25" />
              <RowDefinition Height="25" />
              <RowDefinition Height="25" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="Auto" />
              <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <Label Grid.Row="0" 
            Grid.ColumnSpan="2" Foreground="Yellow" 
            Background="Transparent" 
            FontStyle="Italic" FontFamily="Copperplate Gothic" 
            FontSize="14">Widget One</Label>
            <Button Grid.Row="1" 
            Grid.ColumnSpan="2" Name="WidgetName" 
            Content="{Binding Path=WidgetName}" 
            Height="22" Width="Auto" 
            Foreground="Yellow" FontFamily="Copperplate Gothic" 
            Click="WidgetName_Click">
                <Button.Background>
                   <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                      <GradientStop Color="#FFff0099" Offset="0" />
                      <GradientStop Color="#FF993366" Offset="1" />
                   </LinearGradientBrush>
                </Button.Background>
             </Button>
             <TextBlock Grid.Row="2" 
             Grid.Column="0" FontWeight="Bold" 
             Text="Selected Color:"  Foreground="Yellow" 
             Background="Transparent" FontFamily="Copperplate Gothic" />
             <TextBlock Grid.Row="2" 
             Grid.Column="1" Text="{Binding Path=WidgetColor}" 
             Margin="5,0,0,0" Foreground="Yellow" 
             Background="Transparent" FontFamily="Copperplate Gothic" />
             <TextBlock Grid.Row="3" 
             Grid.Column="0" FontWeight="Bold" 
             Text="State Of Mind:" Foreground="White" 
             Background="Transparent" FontFamily="Copperplate Gothic" />
             <TextBlock Grid.Row="3" 
             Grid.Column="1" Text="{Binding Path=WidgetState}" 
             Margin="5,0,0,0" Foreground="White" 
             Background="Transparent" FontFamily="Copperplate Gothic" />
        </Grid>
        </Border>
     </StackPanel>
</DataTemplate>

So far we have only discussed Widget One, yet the program has three different Widgets, and there can be any number of each Widget. So here is the complete UpdateTreeView routine. It creates three branch nodes, one for each Widget and populates the TreeView Node with the corresponding list of Widgets. Of course Widget's are non-sensical, it is just for demonstration purposes. In real life, this technique could be applied to many things, for example a Lumber Yard Inventory program or even an Auto Parts Database. The possibilities are limitless.


/// <summary>
/// Updates the items in the Treeview
/// This routine will add each of the created Widgets for Widget One, 
/// Two, and Three; each under their own branch 
/// Widget branches are expanded by default
/// </summary>
private void UpdateTreeView()
{
    TreeViewItem tvi_Widget = null;
    // Clear the treeview before update
    _treeView.ItemsSource = null;
    _treeView.Items.Clear();

    // Load up the Treeview - First up Widget One
    if (list_WidgetOne.Count > 0)                   
    // Make sure there are widgets to add before processing
    {
        tvi_Widget = new TreeViewItem();
        tvi_Widget.Header = "Created Widget One(s)";
        _treeView.Items.Add(tvi_Widget);
        foreach (var w_Widget in list_WidgetOne)
        {
            tvi_Widget.Items.Add(w_Widget);
        }
        tvi_Widget.IsExpanded = true;
    }

    // Now load Widget Two
    if (list_WidgetTwo.Count > 0)                   
    // Make sure there are widgets to add before processing
    {
        tvi_Widget = new TreeViewItem();
        tvi_Widget.Header = "Created Widget Two(s)";
        _treeView.Items.Add(tvi_Widget);
        foreach (var w_Widget in list_WidgetTwo)
        {
            tvi_Widget.Items.Add(w_Widget);
        }
        tvi_Widget.IsExpanded = true;
    }
            
    // Now load Widget Three
    if (list_WidgetThree.Count > 0)               
    // Make sure there are widgets to add before processing
    {
        tvi_Widget = new TreeViewItem();
        tvi_Widget.Header = "Created Widget Three(s)";
        _treeView.Items.Add(tvi_Widget);
        foreach (var w_Widget in list_WidgetThree)
        {
            tvi_Widget.Items.Add(w_Widget);
        }
        tvi_Widget.IsExpanded = true;
    }
    // Refresh the treeview
    _treeView.Items.Refresh();
    _treeView.UpdateLayout();

}  // End UpdateTreeView()

In the case of this program where I am using the Xceed PropertyGrid, there is a routine to update the grid using two tracking variables. One to hold the current index of the Widget selected, of course we don't know which Widget that index belongs to; so the other variable is a tracker of which Widget is the active Widget. All Widgets have a button associated with each which call the same routine: WidgetName_Click() Each Widget Name is unique, it is simply a matter of matching the name of the selected Widget. Once matched the index of the Widget and which Widget type are saved to the tracking variables.


// Used to track which Widget is currently selected
public enum WidgetTypes
{
     WidgetOne = 1,
     WidgetTwo,
     WidgetThree
}
// Set to default of Widget one
public static WidgetTypes widgtype_Widget = WidgetTypes.WidgetOne;  
public int int_CurrentWidgetIndex = 0;
.
.
.
/// <summary>
/// A Widget was clicked in the Treeview, determine which one 
/// and display the it's properties to the property grid
/// and set the status label to the name of the Widget
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void WidgetName_Click(object sender, RoutedEventArgs e)
{
    // Index of the widget selected in the treeview
    int int_widget_index = -1;  
    // This will contain the Widget Name
    string string_Widget = e.Source.ToString();
    // Strips windows excess stuff from source name  
    string_Widget = string_Widget.Remove(0, 32);  

    // Determine which Widget was clicked then search the collection for the correct one
    // Widget One Search
    if (string_Widget.Contains("One"))              
    {
        for (int kLoop = 0; kLoop < list_WidgetOne.Count(); kLoop++)
        {
            if (null != list_WidgetOne[kLoop])
            {
                if (list_WidgetOne[kLoop].WidgetName == string_Widget) 
                     { int_widget_index = kLoop; break; }
            }
        }
        // If Widget found, bind to property grid
        if (int_widget_index > -1) void_ShowWidgetOneProperties(int_widget_index);
    }
    // Widget Two search
    else if (string_Widget.Contains("Two"))         
    {
        // Otherwise check Widget Two now
        for (int kLoop = 0; kLoop < list_WidgetTwo.Count(); kLoop++)
        {
            if (null != list_WidgetTwo[kLoop])
            {
                if (list_WidgetTwo[kLoop].WidgetName == string_Widget) 
                     { int_widget_index = kLoop; break; }
            }
        }
        // If Widget found, bind to property grid
        if (int_widget_index > -1) void_ShowWidgetTwoProperties(int_widget_index);
    }
    // Widget Three search
    else if (string_Widget.Contains("Three"))           
    {
        // Check Widget Three now
        for (int kLoop = 0; kLoop < list_WidgetThree.Count(); kLoop++)
        {
            if (null != list_WidgetThree[kLoop])
            {
                if (list_WidgetThree[kLoop].WidgetName == string_Widget) 
                     { int_widget_index = kLoop; break; }
            }
        }
        if (int_widget_index > -1) void_ShowWidgetThreeProperties(int_widget_index);
    }
    else
    {
        //* ERROR *//
    }
}   // End WidgetName_Click()

/// <summary>
/// Shows the selected Widget One in the Property Grid
/// </summary>
/// <param name="intWidgetIndex"></param>
public void 
     void_ShowWidgetOneProperties(int intWidgetIndex)
{
    this.DataContext = list_WidgetOne[intWidgetIndex];
    // Track the index of the currently selected widget one
    int_CurrentWidgetIndex = intWidgetIndex;
    // Widget One is selected    
    widgtype_Widget = WidgetTypes.WidgetOne;    
}

public void 
     void_ShowWidetTwoProperties(int intWidgetIndex)...
public void 
     void_ShowWidgetThreeProperties(int intWidgetIndex)...

One last thing I need to talk about before I just dump the code out there. How do you change Measurement Systems and measurement units within a system. There are four combo boxes defined in the program, one for switching between English (US) and Metric (SI) and one combo box for Length, Area, and Volume respectively. Below is listed the complete XAML and event code for the Bucket System - i.e. changing Measurement Systems.

We keep track of the selected Measurement System with systype_ProgramMeasurement then the Widgets are updated by setting each Widget to the selected measurement system, and fire the TypeChanged event for each Widget which will update all measurements within the Widgets. The PropertyGrid is updated for the current Widget assigned to it's DataContext. Finally, the combos are updated to show the default measurement unit for Length, Area, and Volume in the selected measurement system.

For example, if the Metric System is selected the Length combo is set to the default of "centimeters (SI)". Each of the other combos are updated respectively to their default value as the selected item. All Length Values of Widget One(s) will be converted to the current system.


<!--Buckets Control-->
<ComboBox Name="cmbBucketSystem" 
	Grid.Row="1" 
	Grid.Column="2" 
	Height="30" 
	Width="Auto" 
	Margin="2,2,2,0" 
	VerticalAlignment="Top" 
	FontFamily="Copperplate Gothic" 
	SelectionChanged="cmbBucketSystem_SelectionChanged" 
	Foreground="Yellow" 
	GotFocus="cmbBucketSystem_GotFocus" 
	LostFocus="cmbBucketSystem_LostFocus">
       <ComboBox.Background>
           <LinearGradientBrush 
                       EndPoint="0.5,1"
                       StartPoint="0.5,0">
               <GradientStop Color="#FFff0099"
                    Offset="0" />
               <GradientStop Color="#FF993366"
                    Offset="1" />
           </LinearGradientBrush>
       </ComboBox.Background>
       <ComboBoxItem Content="English System" 
       IsSelected="True"></ComboBoxItem>
       <ComboBoxItem Content="Metric System"></ComboBoxItem>
</ComboBox>

/// <summary>
/// Bucket Measurement System - choose between English or Metric
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void cmbBucketSystem_SelectionChanged
    (object sender, SelectionChangedEventArgs e)
{
    ComboBoxItem cbi_SelectedUnit = (ComboBoxItem)cmbBucketSystem.SelectedItem;
    string str_Content = cbi_SelectedUnit.Content.ToString();
    if (str_Content.Contains("English System")) 
    	systype_ProgramMeasurement = SystemType.UnitedStates;
    else 
    	systype_ProgramMeasurement = SystemType.InternationalSystem;

    UpdateAllWidgets(); // Update all widgets for the measurement system change
    UpdatePropertyGrid();  // Update the current item in the property grid for the measurement system change
    UpdateMeasurementCombos();  // Update the measurement combo boxes for the update
            
}

For each of the Widget collections it is just a matter of cycling through each and set EnumBinding to the selected system and firing the TypeChanged() method.


/// <summary>
/// Update all the Widgets as to the measurement system change
/// </summary>
private void UpdateAllWidgets()
{
    // Update Widget One collection
    for (int kLoop = 0; kLoop < list_WidgetOne.Count(); kLoop++)
    {
        list_WidgetOne[kLoop].EnumBinding = (SystemType)systype_ProgramMeasurement;
        list_WidgetOne[kLoop].TypeChanged();
    }
    // Update Widget Two collection
    for (int kLoop = 0; kLoop < list_WidgetTwo.Count(); kLoop++)
    {
        list_WidgetTwo[kLoop].EnumBinding = (SystemType)systype_ProgramMeasurement;
        list_WidgetTwo[kLoop].TypeChanged();
    }
    // Update Widget Three collection
    for (int kLoop = 0; kLoop < list_WidgetThree.Count(); kLoop++)
    {
        list_WidgetThree[kLoop].EnumBinding = (SystemType)systype_ProgramMeasurement;
        list_WidgetThree[kLoop].TypeChanged();
    }
}  // End UpdateAllWidgets()

Updating the combo boxes to the new units. To avoid null instances the code is wrapped with a flag that is set to true when the program launches. The WindowLoaded() event sets the flag to false.


/// <summary>
/// Routine updates the Measurement System Combos when there is a change in the Measurement System
/// </summary>
private void UpdateMeasurementCombos()
{
    if (!bool_StartingUp)
    {
        // Use Widget One to expose the current measurement system for length
        widgetOne widgetone_Temp = new widgetOne();
        widgetone_Temp.EnumBinding = (SystemType)systype_ProgramMeasurement;
        cmbLengthBuckets.SelectedItem = widgetone_Temp.WidgetLengthBucket;

        // Use Widget Two to expose the current area measurement unit
        widgetTwo widgettwo_Temp = new widgetTwo();
        widgettwo_Temp.EnumBinding = (SystemType)systype_ProgramMeasurement;
        cmbAreaBuckets.SelectedItem = widgettwo_Temp.WidgetAreaBucket;

        // Use Widget Three to get the current volume measurement unit
        widgetThree widgetthree_Temp = new widgetThree();
        widgetthree_Temp.EnumBinding = (SystemType)systype_ProgramMeasurement;
        cmbVolumeBuckets.SelectedItem = widgetthree_Temp.WidgetVolumeBucket;
    }
}  // End UpdateMeasurementCombos

For the Area, Length, and Volume combo boxes, those we will bind in the XAML and use a Selection Change event to provide updates for the new unit selected. We bind the combo boxes directly to the Buckets.Instance and the appropriate unit for the bucket - in the case of Length, it is the LengthBuckets as the ItemsSource and the selected item is the LengthDisplayBucket, the default is "inches (US)". I'll only show one here since the code is the same for each of the other unit selection combo boxes and will be included in the source download.


<ComboBox Name="cmbLengthBuckets" Grid.Row="0" 
    Grid.Column="1" VerticalAlignment="Center" 
    Margin="2,2,2,2" DataContext="{Binding Source={x:Static local:Buckets.Instance}}" 
    ItemsSource="{Binding LengthBuckets}" SelectedItem="{Binding LengthDisplayBucket}" 
    Foreground="Yellow" FontFamily="Copperplate Gothic" 
    GotFocus="cmbLengthBuckets_GotFocus" 
    LostFocus="cmbLengthBuckets_LostFocus" 
    SelectionChanged="cmbLengthBuckets_SelectionChanged">
    <ComboBox.Background>
        <LinearGradientBrush 
            EndPoint="0.5,1"
            StartPoint="0.5,0">
            <GradientStop Color="#FFff0099"
            Offset="0" />
            <GradientStop Color="#FF993366"
            Offset="1" />
        </LinearGradientBrush>
    </ComboBox.Background>
</ComboBox>

/// <summary>
/// Handles the changing of Length Measurement Units
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private 
void cmbLengthBuckets_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    // Avoid doing this if Starting up
    if (bool_StartingUp) return;

    bool bool_Update = false;

    // English Units Indexes = 0 (in) 1 (ft)
    // Metric Units Indexes = 2 (cm) 3 (mm) 4 (m)
    if (systype_ProgramMeasurement == SystemType.UnitedStates)
    {
        if (cmbLengthBuckets.SelectedIndex >= 0 && cmbLengthBuckets.SelectedIndex <= 1)
        {
            bool_Update = true;
        }
        else
        {
            cmbLengthBuckets.SelectedIndex = 0; // Reset to default in (US)
        }
    }
    else  // Metric system being used
    {
        if (cmbLengthBuckets.SelectedIndex >= 2 && cmbLengthBuckets.SelectedIndex <= 4)
        {
            bool_Update = true;
        }
        else
        {
            cmbLengthBuckets.SelectedIndex = 2; // Reset to default cm (SI)
        }
    }
    // Check if update is needed...
    if (bool_Update)
    {
        lb_LengthBucket = (Buckets.Bucket)cmbLengthBuckets.SelectedItem;
        Buckets.Instance.LengthDisplayBucket = 
        	Buckets.Instance.LengthBuckets[cmbLengthBuckets.Items.IndexOf(lb_LengthBucket)];
        UpdatePropertyGrid();
    }
}  // End cmbLengthBuckets_SelectionChanged()

Thanks for bearing with me, I know it was a long article but hopefully I've made some sense out of complex classes and a bit more on WPF. The source code is located here: Widget Manager

End Of Line Man!