All posts in .Net

In today’s blog I am going to give you the method I use to obtain the Windows Product Activation information in Astronomy.

Q: So, where does this information come from?
A: Well, as you may know, you can obtain this information from the following script:
C:\Windows\System32\slmgr.vbs

I always felt this needed to be modified for use in .Net, so I refactored this a bit and created my own replacement method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/// <summary>
/// Retrieves licensing information.
/// </summary>
/// <param name="managementScope">The management scope.</param>
/// <returns></returns>
public static string GetLicensingInfo(ManagementScope managementScope)
{
    // Output
    string result = "Information Unavailable";
    const string windowsAppId = "55c92734-d682-4d71-983e-d6ec3f16059f";
 
    // Default values
    const uint HR_SL_E_GRACE_TIME_EXPIRED = 0xC004F009;
    const uint HR_SL_E_NOT_GENUINE = 0xC004F200;
 
    // Set WMI query
    var query =
        new WqlObjectQuery(
            "SELECT LicenseStatus, GracePeriodRemaining " +
            "FROM SoftwareLicensingProduct " +
            "WHERE (PartialProductKey <> NULL) AND " +
            "(ApplicationId='" + windowsAppId + "')");
 
    // Get items
    using (var searcher = new ManagementObjectSearcher(managementScope, query))
     
    // Get data
    using (var colItems = searcher.Get())
    {
        foreach (ManagementBaseObject obj in colItems)
        {
            string subline;
 
            // License Status
            int licenseStatus = Convert.ToInt32(obj.GetPropertyValue("LicenseStatus"));
 
            // Time remaining: minutes / days
            int gpMin = Convert.ToInt32(obj.GetPropertyValue("GracePeriodRemaining"));
            int gpDay = gpMin / (24 * 60);
 
            // Evaluate
            switch (licenseStatus)
            {
                case 0:
                    result = "Unlicensed";
                    break;
 
                case 1:
                    result = "Licensed";
                    if (gpMin > 0)
                    {
                        subline =
                            String.Format(
                                "Activation expiration: " +
                                "{0} minute(s) ({1} day(s))", gpMin, gpDay);
                        result += "\n" + subline;
                    }
                    break;
 
                case 2:
                    result = "Initial grace period";
                    subline =
                        String.Format("Time remaining: {0} minute(s) " +
                                        "({1} day(s))", gpMin, gpDay);
                    result += "\n" + subline;
                    break;
 
                case 3:
                    result =
                        "Additional grace period (KMS license expired or " +
                        "hardware out of tolerance)";
                    subline =
                        String.Format("Time remaining: {0} minute(s) " +
                                        "({1} day(s))", gpMin, gpDay);
                    result += "\n" + subline;
                    break;
 
                case 4:
                    result = "Non-genuine grace period.";
                    subline =
                        String.Format("Time remaining: {0} minute(s) " +
                                        "({1} day(s))", gpMin, gpDay);
                    result += "\n" + subline;
                    break;
 
                case 5:
                    result = "Notification";
 
                    uint licenseStatusReason;
                    uint.TryParse(obj.GetPropertyValue("GracePeriodRemaining").ToString(),
                        out licenseStatusReason);
                         
                    // Evaluate
                    switch (licenseStatusReason)
                    {
                        case HR_SL_E_NOT_GENUINE:
                            subline =
                                String.Format(
                                    "Notification Reason: 0x{0} " +
                                    "(non-genuine).", licenseStatusReason);
                            break;
 
                        case HR_SL_E_GRACE_TIME_EXPIRED:
                            subline =
                                String.Format(
                                    "Notification Reason: 0x{0} " +
                                    "(grace time expired).", licenseStatusReason);
                            break;
 
                        default:
                            subline =
                                String.Format("Notification Reason: 0x{0}.",
                                                licenseStatusReason);
                            break;
                    }
                    result += "\n" + subline;
                    break;
 
                case 6:
                    result = "Extended grace period";
                    subline =
                        String.Format("Time remaining: {0} minute(s) " +
                                        "({1} day(s))", gpMin, gpDay);
                    result += "\n" + subline;
                    break;
 
                default:
                    result = "Unknown";
                    break;
            }
        }
    }
     
    // return
    return result;
}

An overboard implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/// <summary>
/// Connects to a target computer through Kerberos authentication.
/// </summary>
/// <param name="computername">The computername.</param>
/// <param name="user">The user.</param>
/// <param name="securePass">The secure pass.</param>
/// <returns>
/// A ManagementScope context for the current connection.
/// </returns>
public static ManagementScope Connect(string computername, string user,
    SecureString securePass)
{
    // Build an options object for the remote connection
    var options = new ConnectionOptions
        {
            Impersonation = ImpersonationLevel.Impersonate,
            EnablePrivileges = true
        };
 
    // Set properties
    // Check name
    if (String.IsNullOrEmpty(computername))
    {
        computername = ".";
    }
 
    // Check credentials
    // Cannot pass a blank user and password.
    if (!String.IsNullOrEmpty(user))
    {
        options.Username = user;
        options.SecurePassword = securePass;
    }
     
    // Make a connection to a remote computer.
    var managementScope =
        new ManagementScope($@"\{computername}\root\cimv2", options);
 
    try
    {
        // Connect
        managementScope.Connect();
    }
    catch (Exception ex)
    {
        throw ex;
    }
     
    // return
    return managementScope;
}
 
/// <summary>
/// Gets the license you so desperately want.
/// </summary>
public void GetMyLicense()
{
    // Get the management scope
    var myManagementScope = Connect(".", "XCALIBUR\jarzt", {my secure password});
 
    // Get the licensing
    string licensing = GetLicensingInfo(myManagementScope);
 
    // Report to client
    MessageBox.Show("My current licensing status: " + licensing);
}

So, really that’s all there is to it.

Happy coding!

One of the issues I have seen around the web is in regard to TabControls and missing content. The “missing” content in this case is the content of tab items within a TabControl that are not in focus. By design, a TabControl will only load the content of a TabItem once it has been brought into focus. This is not bad design if you think about it for a moment.

Q: Shouldn’t we just load into memory what we need at the time we need it to minimize our footprint?
A: Yes, definitely. This is good application design.

But, what about the case where I need information from other tabs that have not yet been brought into focus? For example, what if I have a GridView in another tab and I want to show the row count right when the control is loaded? Well, the answer to that is pre-loading your tab items. And since the TabControl does not have this behavior built-in by default, we will have to do it ourselves.

Here is one way I saw on the web that I wanted to bring to your attention:

1
2
3
4
5
for (var i = 0; i < myTabControl.Items.Count; i++)
{
    myTabControl.SelectedIndex = i;
    myTabControl.UpdateLayout();
}

Well, this looks pretty good and it has very little code. This should work, right? No, not in all cases. The reason is that this will cycle through the tabs as fast as the UpdateLayout() call completes on each tab. Because this implementation will not necessarily wait until each tab is done loading, we cannot guarantee that the content of each tab has finished loading into the Visual Tree. What we need is a solution that waits until each tab item has completely loaded its content before we move to the next tab.

So, let’s state our list of objectives:

  • Objective 1: Each tab item should wait to complete loading before the next tab item is selected.
  • Objective 2: We should hide the Tab Control from view until it is done pre-loading.
  • Objective 3: We should return to the first tab in the sequence at completion.

The approach:

In order to effectively meet the first objective, we should chain the loading of each tab through recursion.

So, here are our 2 methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/// <summary>
/// Preloads tab items of a tab control in sequence.
/// </summary>
/// <param name="tabControl">The tab control.</param>
public static void PreloadTabs(TabControl tabControl)
{
    // Evaluate
    if (tabControl.Items != null)
    {
        // The first tab is already loaded
        // so, we will start from the second tab.
        if (tabControl.Items.Count > 1)
        {
            // Hide tabs
            tabControl.Opacity = 0.0;
 
            // Last action
            Action onComplete = () =>
            {
                // Set index to first tab
                tabControl.SelectedIndex = 0;
 
                // Show tabs
                tabControl.Opacity = 1.0;
            };
 
            // Second tab
            var firstTab = (tabControl.Items[1] as TabItem);
            if (firstTab != null)
            {
                PreloadTab(tabControl, firstTab, onComplete);
            }
        }
    }
}
 
/// <summary>
/// Preloads an individual tab item.
/// </summary>
/// <param name="tabControl">The tab control.</param>
/// <param name="tabItem">The tab item.</param>
/// <param name="onComplete">The onComplete action.</param>
private static void PreloadTab(TabControl tabControl, TabItem tabItem, Action onComplete = null)
{
    // On update complete
    tabItem.Loaded += delegate
    {
        // Update if not the last tab
        if (tabItem != tabControl.Items.Last())
        {
            // Get next tab
            var nextIndex = tabControl.Items.IndexOf(tabItem) + 1;
            var nextTabItem = tabControl.Items[nextIndex] as TabItem;
 
            // Preload
            if (nextTabItem != null)
            {
                PreloadTab(tabControl, nextTabItem, onComplete);
            }
        }
 
        else
        {
            if (onComplete != null)
            {
                onComplete();
            }
        }
    };
 
    // Set current tab context
    tabControl.SelectedItem = tabItem;
}

So what’s going on here?

  1. PreloadTabs acts as our public method where we pass the TabControl.
  2. The opacity of the TabControl is set to 0 (Objective 2). We do not change the visibility in this case because the UI will not properly update if Visibility is Collapsed.
  3. PreloadTabs will make sure it has tab items and then call our private method PreloadTab for the second tab in the collection (since the first tab would already be loaded by the instantiation of the TabControl).
  4. PreloadTab evaluates the second tab item in the collection by setting the TabControl.SelectedItem property to the current tab.
  5. Once the tab item has completed loading, it will be evaluated. (Objective 1)
  6. If it is not the last tab item in the collection, the next tab item is retrieved and passed to the PreloadTab method.
  7. Note: This behavior will repeat until the last item in the tab item collection has been reached.
  8. Once the last item has been reached, the onComplete action is called.
  9. When the onComplete action is called, we set the SelectedIndex of the TabControl back to 0 (Objective 2), and then restore the opacity of the TabControl back to 1 (Objective 3).

And just like that we have a tab pre-loader for our TabControl.

That’s all for now. Happy coding.

A different take on Deep Copy

Categories: .Net, C#, Clone, Silverlight, WPF
Comments: 1

This article has been revised here.

I have seen several examples of different methods to perform a deep copy in C#. The most common one is with a Stream, implemented like this:

1
2
3
4
5
6
7
8
9
10
11
public static T DeepClone<T>(T obj)
{
 using (var ms = new MemoryStream())
 {
   var formatter = new BinaryFormatter();
   formatter.Serialize(ms, obj);
   ms.Position = 0;
 
   return (T) formatter.Deserialize(ms);
 }
}

That is certainly one way to do it but it won’t work in Silverlight.

The suggested Sliverlight approach:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static T DeepCopy<T>(this T oSource)
{
    T oClone;
    DataContractSerializer dcs = new DataContractSerializer(typeof(T));
 
    using (MemoryStream ms = new MemoryStream())
    {
        dcs.WriteObject(ms, oSource);
        ms.Position = 0;
        oClone = (T)dcs.ReadObject(ms);
    }
 
    return oClone;
}

All right, that works in Silverlight but it requires that all objects passed are Serializable. That is actually a bit of a daunting task for custom objects at times, and we should try to use a simpler more universal approach.

So, I suggest using reflection:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/// <summary>
/// Copies all public properties from one object to another.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="destination">The destination.</param>
/// <exception cref="System.Exception">Source and destination cannot be null and must be
/// of the same type!</exception>
public static void DeepCopy(object source, object destination)
{
    if ((source != null &amp;&amp; destination != null) &amp;&amp;
        (source.GetType() == destination.GetType()))
    {
        // Get properties
        var propertyInfos = source.GetType().GetProperties();
        if (propertyInfos.Length <= 0) return;
 
        // Evaluate
        foreach (var propInfo in propertyInfos)
        {
            // Process only public properties
            if (!propInfo.CanWrite) continue;
 
            // Get value from source and assign to destination
            object value = propInfo.GetValue(source, null);
            propInfo.SetValue(destination, value, null);
            if (value == null) continue;
 
            // Check for properties and propogate if they exist
            var newPropInfos = value.GetType().GetProperties();
            if (newPropInfos.Length > 0)
            {
                // Copy properties for each child where necessary
                DeepCopy(
                    source.GetType().GetProperty(propInfo.Name),
                    destination.GetType().GetProperty(propInfo.Name));
            }
        }
    }
 
    else
    {
        throw new Exception(
            "Source and destination cannot be null and must be of " +
            "the same type!");
    }
}

This will work in Silverlight because of the if block for propInfo.CanWrite. This is added because based on Silverlights rigid security constraints we can only copy public properties.

That’s all for now.

In this post I am going to introduce the idea of subscribing to property change events in our Model Base.

Note: Please read Part I before continuing here.
Note: Please read Part II before continuing here.

The purpose of this is to cover a potentially annoying situation:

Scenario: Let’s say you have a ViewModel, and inside that you have another ViewModel acting as a property like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private MyCustomObject _customObjectInstance;
 
public MyCustomObject CustomObjectInstance
{
    get
    {
        return _customObjectInstance;
    }
 
    set
    {
        SetValue("CustomObjectInstance", ref _customObjectInstance, ref value);
    }
}

Okay, now you want your UI bound to the current ViewModel to change every time CustomObjectInstance.MeterReading changes.

Q: (Panic moment) So, how do I do that without breaking my wonderful abstraction?
A: Implementing an ability to Subscribe to a nested property. All it takes is a little reflection and patience.

The idea is that we tell that property to fire a specific Action whenever it is changed in our ViewModelBase.

The first thing we will need to add is a private list of subscriptions:

1
2
3
4
/// <summary>
/// Subscription list.
/// </summary>
private readonly List<Tuple<string, Action<object>>> _subscriptions;

Instead of creating yet another custom object, I decided to use a Tuple because they are convenient.

  • The string value will serve as the name of the property you want to subscribe to.
  • The Action will serve as the Action you wish to call when the property is changed. The object parameter allows you to return an object if needed.

Next, we make sure to create a new instance of _subscriptions in the Constructor:

1
2
3
4
5
6
7
/// <summary>
/// Default constructor.
/// </summary>
protected ViewModelBase()
{
    _subscriptions = new List<Tuple<string, Action<object>>>();
}

Now we will expose a Subscribe method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/// <summary>
/// Subscribes an action to a specific property that will be called
/// during that property's OnPropertyChanged event.
/// </summary>
/// <param name="propertyName"></param>
/// <param name="onChange"></param>
public void Subscribe(string propertyName, Action<object> onChange)
{
    // Verify property
    var propInfo = this.GetType().GetProperty(propertyName);
 
    // If valid, add to subscription pool.
    if (propInfo != null)
    {
        _subscriptions.Add(
            new Tuple<string, Action<object>>(propertyName, onChange));
    }
    else
    {
        // Invalid property name provided.
        throw new Exception(
            "Property "" + propertyName + "" could not be " +
            "found for type "" + this.GetType().ToString() + ""!");
    }
}

This idea here is fairly simple:

  • We pass our property name and intended Action that will fire OnPropertyChanged.
  • If the property name is not valid, we will throw an exception to ensure we didn’t pass invalid information into our Subscribe method.

Q: Alright, now we have a nice Tuple-list full of property names and Actions. Now what?
A: Glad you asked. Here comes the hard part:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/// <summary>
/// Processes existing subscriptions matching the provided property name.
/// </summary>
/// <param name="propertyName"></param>
private void ProcessSubscriptions(string propertyName)
{
    // Get matching subscriptions
    var subList =
        (from p in _subscriptions
         where p.Item1 == propertyName
         select p).ToList();
 
    // Check if any matches were found.
    if (subList.Any())
    {
        // Process actions
        foreach (var sub in subList)
        {
            // Evaluate action
            var onChange = sub.Item2;
            if (onChange != null)
            {
                // Get property value by name
                var propInfo = this.GetType().GetProperty(propertyName);
                var propValue = propInfo.GetValue(this, null);
 
                // Invoke action
                onChange(propValue);
            }
        }
    }
}

ProcessSubscriptions does the following:

  • Looks up a specific property by name in _subscriptions and gets a list of all entries that are registered for that property.
  • Loop: If a specific entry has a valid Action assigned to it, it will use reflection to get that property value and pass it to the action (as out object parameter mentioned earlier).

So, the last piece is making sure ProcessSubscriptions is fired when the property has been changed. And that is as easy as augmenting our trusted OnPropertyChanged method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// <summary>
/// Calls the PropertyChanged event
/// </summary>
/// <param name="propertyName"></param>
/// <param name="onChanged"></param>
protected void OnPropertyChanged(string propertyName, Action onChanged = null)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
        // Call handler
        handler(this, new PropertyChangedEventArgs(propertyName));
 
        // Subscriptions
        ProcessSubscriptions(propertyName);
 
        // On changed
        if (onChanged != null)
        {
            onChanged();
        }
    }
}

No worries if your specific property being changed is without entries. ProcessSubscriptions only acts on what is present in _subscriptions, so no entries means it just moves on.

Here is how you would use it in your parent ViewModel:

1
CustomObjectInstance.Subscribe("MeterReading", obj => MyActionThatDoesStuff());

Now, every time CustomObjectInstance.MeterReading is updated, the MyActionThatDoesStuff Action will be called allowing you to always have the latest values from your nested properties.

Here is our new ViewModelBase in it’s entirety:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/// <summary>
/// Extends the INotifyPropertyChanged interface to the class properties.
/// </summary>
public abstract class ViewModelBase : INotifyPropertyChanged
{
    #region Members
 
    /// <summary>
    /// Subscription list.
    /// </summary>
    private readonly List<Tuple<string, Action<object>>> _subscriptions;
 
    #endregion
 
    #region Constructors
 
    /// <summary>
    /// Default constructor.
    /// </summary>
    protected ViewModelBase()
    {
        _subscriptions = new List<Tuple<string, Action<object>>>();
    }
 
    #endregion
 
    #region Methods
 
    /// <summary>
    /// To be used within the "set" accessor in each property.
    /// This invokes the OnPropertyChanged method.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <param name="value"></param>
    /// <param name="newValue"></param>
    /// <param name="onChanged"></param>
    protected void SetValue<T>(string name, ref T value, ref  T newValue,
        Action onChanged = null)
    {
        if (newValue != null)
        {
            if (!newValue.Equals(value))
            {
                value = newValue;
                OnPropertyChanged(name, onChanged);
            }
        }
        else
        {
            value = default(T);
        }
    }
 
    #endregion
 
    #region INotifyPropertyChanged
 
    /// <summary>
    /// The PropertyChanged event handler.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;
 
    /// <summary>
    /// Calls the PropertyChanged event
    /// </summary>
    /// <param name="propertyName"></param>
    /// <param name="onChanged"></param>
    protected void OnPropertyChanged(string propertyName, Action onChanged = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            // Call handler
            handler(this, new PropertyChangedEventArgs(propertyName));
 
            // Subscriptions
            ProcessSubscriptions(propertyName);
 
            // On changed
            if (onChanged != null)
            {
                onChanged();
            }
        }
    }
 
    /// <summary>
    /// Subscribes an action to a specific property that will be called
    /// during that property's OnPropertyChanged event.
    /// </summary>
    /// <param name="propertyName"></param>
    /// <param name="onChange"></param>
    public void Subscribe(string propertyName, Action<object> onChange)
    {
        // Verify property
        var propInfo = this.GetType().GetProperty(propertyName);
 
        // If valid, add to subscription pool.
        if (propInfo != null)
        {
            _subscriptions.Add(
                new Tuple<string, Action<object>>(propertyName, onChange));
        }
        else
        {
            // Invalid property name provided.
            throw new Exception(
                "Property "" + propertyName + "" could not be " +
                "found for type "" + this.GetType().ToString() + ""!");
        }
    }
     
    /// <summary>
    /// Clears the subscriptions.
    /// </summary>
    public void ClearSubscriptions()
    {
        _subscriptions.Clear();
    }
 
    /// <summary>
    /// Processes existing subscriptions matching the provided property name.
    /// </summary>
    /// <param name="propertyName"></param>
    private void ProcessSubscriptions(string propertyName)
    {
        // Get matching subscriptions
        var subList =
            (from p in _subscriptions
             where p.Item1 == propertyName
             select p).ToList();
 
        // Check if any matches were found.
        if (subList.Any())
        {
            // Process actions
            foreach (var sub in subList)
            {
                // Evaluate action
                var onChange = sub.Item2;
                if (onChange != null)
                {
                    // Get property value by name
                    var propInfo = this.GetType().GetProperty(propertyName);
                    var propValue = propInfo.GetValue(this, null);
 
                    // Invoke action
                    onChange(propValue);
                }
            }
        }
    }
 
    #endregion
}

I hope this has been helpful for you.

In this article, we are going to look at calling an Action when our property changes.

Note: Please read Part I before continuing here.

Alright, so let’s look at our ViewModelBase from Part I:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/// <summary>
/// Extends the INotifyPropertyChanged interface to the class properties.
/// </summary>
public abstract class ViewModelBase : INotifyPropertyChanged
{
    #region Methods
 
    /// <summary>
    /// To be used within the "set" accessor in each property.
    /// This invokes the OnPropertyChanged method.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <param name="value"></param>
    /// <param name="newValue"></param>
    protected void SetValue<T>(string name, ref T value, ref  T newValue)
    {
        if (newValue != null)
        {
            if (!newValue.Equals(value))
            {
                value = newValue;
                OnPropertyChanged(name);
            }
        }
        else
        {
            value = default(T);
        }
    }
 
    #endregion
 
    #region INotifyPropertyChanged
 
    /// <summary>
    /// The PropertyChanged event handler.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;
     
    /// <summary>
    /// Calls the PropertyChanged event
    /// </summary>
    /// <param name="propertyName"></param>
    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
 
    #endregion
}

Q: Okay, so what now?
A: Glad you asked. Let’s augment the above class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/// <summary>
/// Extends the INotifyPropertyChanged interface to the class properties.
/// </summary>
public abstract class ViewModelBase : INotifyPropertyChanged
{
    #region Methods
 
    /// <summary>
    /// To be used within the "set" accessor in each property.
    /// This invokes the OnPropertyChanged method.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <param name="value"></param>
    /// <param name="newValue"></param>
    protected void SetValue<T>(string name, ref T value, ref  T newValue, Action onChanged = null)
    {
        if (newValue != null)
        {
            if (!newValue.Equals(value))
            {
                value = newValue;
                OnPropertyChanged(name, onChanged);
            }
        }
        else
        {
            value = default(T);
        }
    }
 
    #endregion
 
    #region INotifyPropertyChanged
 
    /// <summary>
    /// The PropertyChanged event handler.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;
     
    /// <summary>
    /// Calls the PropertyChanged event
    /// </summary>
    /// <param name="propertyName"></param>
    protected void OnPropertyChanged(string propertyName, Action onChanged = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
 
            // On changed
            if (onChanged != null)
            {
                onChanged();
            }
        }
    }
 
    #endregion
}

Essentially, we added an Action as a parameter to the SetValue method. This is then passed whenever the OnPropertyChanged method is called and executed after the event fires (if it is not null).

Using it is as easy as this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private string phoneNumberValue = String.Empty;
 
public string PhoneNumber
{
    get
    {
        return this.phoneNumberValue;
    }
 
    set
    {
        SetValue("PhoneNumber", ref this.phoneNumberValue, ref value, () => { UpdateSomeOtherUISection(); });
    }
}

It’s not a common need, but sometimes something like this can really get you out of a bind when you need to update other areas of your application when this property changes.

Try it sometime.

Having used properties in an ObservableCollection, you are probably familiar with implementing INotifyPropertyChanged in your custom objects.

A property utilizing this implementation would look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private string phoneNumberValue = String.Empty;
 
public string PhoneNumber
{
    get
    {
        return this.phoneNumberValue;
    }
 
    set
    {
        if (value != this.phoneNumberValue)
        {
            this.phoneNumberValue = value;
            NotifyPropertyChanged("PhoneNumber");
        }
    }
}

To remedy this repetitive implementation, I created this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/// <summary>
/// Extends the INotifyPropertyChanged interface to the class properties.
/// </summary>
public abstract class ViewModelBase : INotifyPropertyChanged
{
    #region Methods
 
    /// <summary>
    /// To be used within the "set" accessor in each property.
    /// This invokes the OnPropertyChanged method.
    /// </summary>
    /// <typeparam name="T"></param>
    /// <param name="name"></param>
    /// <param name="value"></param>
    /// <param name="newValue"></param>
    protected void SetValue&amp;amp;amp;amp;lt;T&amp;amp;amp;amp;gt;(string name, ref T value, ref  T newValue)
    {
        if (newValue != null)
        {
            if (!newValue.Equals(value))
            {
                value = newValue;
                OnPropertyChanged(name);
            }
        }
        else
        {
            value = default(T);
        }
    }
 
    #endregion
 
    #region INotifyPropertyChanged
 
    /// <summary>
    /// The PropertyChanged event handler.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;
 
    /// <summary>
    /// Calls the PropertyChanged event
    /// </summary>
    /// <param name="propertyName"></param>
    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
 
    #endregion
}

Then, all we need to do is inherit ViewModelBase in our custom object.

Now, our property would look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private string phoneNumberValue = String.Empty;
 
public string PhoneNumber
{
    get
    {
        return this.phoneNumberValue;
    }
 
    set
    {
        SetValue("PhoneNumber", ref this.phoneNumberValue, ref value);
    }
}

So, what was the reason for doing this?

  1. It avoids directly implementing INotifyPropertyChanged. This allows the ability to start up objects with this implementation much faster.
  2. Less coding and potential messes involving the set operator.

Update as of August 2022

Good news is since all the C# goodness over the last 10 years, I have greatly improved this implementation. It can be used like this:

1
2
3
4
5
6
7
private string _phoneNumberValue = String.Empty;
 
public string PhoneNumber
{
    get => _phoneNumberValue;
    set => NotifyOfChange(value, ref _phoneNumberValue);
}

Simple, right?

Customizing the WPF MenuItem in XAML

Categories: .Net, C#, WPF, XAML
Comments: 4

Note: This topic has been updated with a complete solution in Part 2.

So, I was spending some time trying to figure out how to make a menu in WPF scrollable before it maxed out on my screen height. Well, unfortunately, the only way to do it is to override the default Aero template for the MenuItem in WPF. You can get that here.

Here’s the section to change. Notice the addition of “MaxHeight” on the “SubMenuScrollViewer” object. It’s really that simple.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<Popup x:Name="PART_Popup"
    AllowsTransparency="true"
    Placement="Right"
    VerticalOffset="-3"
    HorizontalOffset="-2"
    IsOpen="{Binding Path=IsSubmenuOpen,RelativeSource={RelativeSource TemplatedParent}}"
    Focusable="false"
    PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}">
    <theme:SystemDropShadowChrome Name="Shdw" Color="Transparent">
        <ContentControl Name="SubMenuBorder"
            Template="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=SubmenuContent}}"
            IsTabStop="false">
            <ScrollViewer Name="SubMenuScrollViewer" CanContentScroll="true" MaxHeight="400"
                Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=MenuScrollViewer}}">
                <Grid RenderOptions.ClearTypeHint="Enabled">
                    <Canvas Height="0" Width="0" HorizontalAlignment="Left" VerticalAlignment="Top">
                        <Rectangle
                            Height="{Binding ElementName=SubMenuBorder,Path=ActualHeight}" 
                            Width="{Binding ElementName=SubMenuBorder,Path=ActualWidth}" 
                            Fill="{StaticResource SubMenuBackgroundBrush}" />
                    </Canvas>
                    <ItemsPresenter Name="ItemsPresenter" Margin="2"
                        KeyboardNavigation.TabNavigation="Cycle"
                        KeyboardNavigation.DirectionalNavigation="Cycle"
                        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                        Grid.IsSharedSizeScope="true"/>
                </Grid>
            </ScrollViewer>
        </ContentControl>
    </theme:SystemDropShadowChrome>
</Popup>

 

Here is a screenshot of the desired effect:

Example of Height-Controlled Menu

Example of Height-Controlled Menu

Here is the full Resource Dictionary xaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
<ResourceDictionary
  xmlns:theme="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero">
 
    <MenuScrollingVisibilityConverter x:Key="MenuScrollingVisibilityConverter"/>
    <Geometry x:Key="DownArrow">M 0,0 L 3.5,4 L 7,0 Z</Geometry>
    <Geometry x:Key="UpArrow">M 0,4 L 3.5,0 L 7,4 Z</Geometry>
    <Geometry x:Key="RightArrow">M 0,0 L 4,3.5 L 0,7 Z</Geometry>
    <Geometry x:Key="Checkmark">M 0,5.1 L 1.7,5.2 L 3.4,7.1 L 8,0.4 L 9.2,0 L 3.3,10.8 Z</Geometry>
    <SolidColorBrush x:Key="SubMenuBackgroundBrush" Color="#FFF5F5F5" />
 
    <LinearGradientBrush x:Key="MenuItemSelectionFill"
                         StartPoint="0,0"
                         EndPoint="0,1">
        <LinearGradientBrush.GradientStops>
            <GradientStop Color="#34C5EBFF"
                          Offset="0"/>
            <GradientStop Color="#3481D8FF"
                          Offset="1"/>
        </LinearGradientBrush.GradientStops>
    </LinearGradientBrush>
    <LinearGradientBrush x:Key="MenuItemPressedFill"
                         StartPoint="0,0"
                         EndPoint="0,1">
        <LinearGradientBrush.GradientStops>
            <GradientStop Color="#28717070"
                          Offset="0"/>
            <GradientStop Color="#50717070"
                          Offset="0.75"/>
            <GradientStop Color="#90717070"
                          Offset="1"/>
        </LinearGradientBrush.GradientStops>
    </LinearGradientBrush>
    <Style x:Key="{x:Static MenuItem.SeparatorStyleKey}"
           TargetType="{x:Type Separator}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Separator}">
                    <Grid SnapsToDevicePixels="true" Margin="0,6,0,4">
                        <Rectangle Height="1"
                                   Margin="30,0,1,1"
                                   Fill="#E0E0E0"/>
                        <Rectangle Height="1"
                                   Margin="30,1,1,0"
                                   Fill="White"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=TopLevelItemTemplateKey}"
                     TargetType="{x:Type MenuItem}">
        <Grid SnapsToDevicePixels="true">
            <Rectangle x:Name="OuterBorder"
                       RadiusX="2"
                       RadiusY="2"/>
            <Rectangle Name="Bg"
                       Margin="1"
                       Fill="{TemplateBinding MenuItem.Background}"
                       Stroke="{TemplateBinding MenuItem.BorderBrush}"
                       StrokeThickness="1"
                       RadiusX="1"
                       RadiusY="1"/>
            <Rectangle x:Name="InnerBorder"
                       Margin="2"/>
            <DockPanel>
                <ContentPresenter x:Name="Icon"
                                  Margin="4,0,6,0"
                                  VerticalAlignment="Center"
                                  ContentSource="Icon"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                <Path x:Name="GlyphPanel"
                      Margin="7,0,0,0"
                      Visibility="Collapsed"
                      VerticalAlignment="Center"
                      Fill="{TemplateBinding MenuItem.Foreground}"
                      FlowDirection="LeftToRight"
                      Data="{StaticResource Checkmark}"/>
                <ContentPresenter ContentSource="Header"
                                  Margin="{TemplateBinding MenuItem.Padding}"
                                  RecognizesAccessKey="True"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
            </DockPanel>
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="Icon"
                     Value="{x:Null}">
                <Setter TargetName="Icon"
                        Property="Visibility"
                        Value="Collapsed"/>
            </Trigger>
            <Trigger Property="IsChecked"
                     Value="true">
                <Setter TargetName="GlyphPanel"
                        Property="Visibility"
                        Value="Visible"/>
                <Setter TargetName="Icon"
                        Property="Visibility"
                        Value="Collapsed"/>
            </Trigger>
            <Trigger Property="IsHighlighted"
                     Value="true">
                <Setter TargetName="Bg"
                        Property="Stroke"
                        Value="#90717070"/>
                <Setter TargetName="OuterBorder"
                        Property="Stroke"
                        Value="#50FFFFFF"/>
                <Setter TargetName="InnerBorder"
                        Property="Stroke"
                        Value="#50FFFFFF"/>
 
            </Trigger>
            <Trigger Property="IsKeyboardFocused"
                     Value="true">
                <Setter TargetName="Bg"
                        Property="Stroke"
                        Value="#E0717070"/>
                <Setter TargetName="Bg"
                        Property="Fill"
                        Value="{StaticResource MenuItemPressedFill}"/>
                <Setter TargetName="InnerBorder"
                        Property="Stroke"
                        Value="#50747272"/>
            </Trigger>
            <Trigger Property="IsEnabled"
                     Value="false">
                <Setter Property="Foreground"
                        Value="#FF9A9A9A"/>
                <Setter TargetName="GlyphPanel"
                        Property="Fill"
                        Value="#848589"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
    <ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=TopLevelHeaderTemplateKey}"
                     TargetType="{x:Type MenuItem}">
        <Grid SnapsToDevicePixels="true">
            <Rectangle x:Name="OuterBorder"
                       RadiusX="2"
                       RadiusY="2"/>
            <Rectangle Name="Bg"
                       Margin="1"
                       Fill="{TemplateBinding MenuItem.Background}"
                       Stroke="{TemplateBinding MenuItem.BorderBrush}"
                       StrokeThickness="1"
                       RadiusX="1"
                       RadiusY="1"/>
            <Rectangle x:Name="InnerBorder"
                       Margin="2"/>
            <DockPanel>
                <ContentPresenter x:Name="Icon"
                                  Margin="4,0,6,0"
                                  VerticalAlignment="Center"
                                  ContentSource="Icon"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                <Path x:Name="GlyphPanel"
                      Margin="7,0,0,0"
                      Visibility="Collapsed"
                      VerticalAlignment="Center"
                      Fill="{TemplateBinding MenuItem.Foreground}"
                      FlowDirection="LeftToRight"
                      Data="{StaticResource Checkmark}"/>
                <ContentPresenter ContentSource="Header"
                                  Margin="{TemplateBinding MenuItem.Padding}"
                                  RecognizesAccessKey="True"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
            </DockPanel>
            <Popup x:Name="PART_Popup"
                   HorizontalOffset="1"
                   VerticalOffset="-1"
                   AllowsTransparency="true"
                   Placement="Bottom"
                   IsOpen="{Binding Path=IsSubmenuOpen,RelativeSource={RelativeSource TemplatedParent}}"
                   Focusable="false"
                   PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}">
                <theme:SystemDropShadowChrome Name="Shdw"
                                              Color="Transparent">
                    <ContentControl Name="SubMenuBorder"
                                    Template="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=SubmenuContent}}"
                                    IsTabStop="false">
                        <ScrollViewer Name="SubMenuScrollViewer" CanContentScroll="true" MaxHeight="400"
                                      Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=MenuScrollViewer}}">
                            <Grid RenderOptions.ClearTypeHint="Enabled">
                                <Canvas Height="0" Width="0" HorizontalAlignment="Left" VerticalAlignment="Top">
                                    <Rectangle
                                    Height="{Binding ElementName=SubMenuBorder,Path=ActualHeight}" 
                                    Width="{Binding ElementName=SubMenuBorder,Path=ActualWidth}" 
                                    Fill="{StaticResource SubMenuBackgroundBrush}" />
                                </Canvas>
                                <ItemsPresenter Name="ItemsPresenter" Margin="2"
                                            KeyboardNavigation.TabNavigation="Cycle"
                                            KeyboardNavigation.DirectionalNavigation="Cycle"
                                            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                            Grid.IsSharedSizeScope="true"/>
                            </Grid>
                        </ScrollViewer>
                    </ContentControl>
                </theme:SystemDropShadowChrome>
            </Popup>
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="IsSuspendingPopupAnimation"
                     Value="true">
                <Setter TargetName="PART_Popup"
                        Property="PopupAnimation"
                        Value="None"/>
            </Trigger>
            <Trigger Property="Icon"
                     Value="{x:Null}">
                <Setter TargetName="Icon"
                        Property="Visibility"
                        Value="Collapsed"/>
            </Trigger>
            <Trigger Property="IsChecked"
                     Value="true">
                <Setter TargetName="GlyphPanel"
                        Property="Visibility"
                        Value="Visible"/>
                <Setter TargetName="Icon"
                        Property="Visibility"
                        Value="Collapsed"/>
            </Trigger>
            <Trigger SourceName="PART_Popup"
                     Property="Popup.HasDropShadow"
                     Value="true">
                <Setter TargetName="Shdw"
                        Property="Margin"
                        Value="0,0,5,5"/>
                <Setter TargetName="Shdw"
                        Property="Color"
                        Value="#71000000"/>
            </Trigger>
            <Trigger Property="IsHighlighted"
                     Value="true">
                <Setter TargetName="Bg"
                        Property="Stroke"
                        Value="#90717070"/>
                <Setter TargetName="OuterBorder"
                        Property="Stroke"
                        Value="#50FFFFFF"/>
                <Setter TargetName="InnerBorder"
                        Property="Stroke"
                        Value="#50FFFFFF"/>
 
            </Trigger>
            <Trigger Property="IsKeyboardFocused"
                     Value="true">
                <Setter TargetName="Bg"
                        Property="Stroke"
                        Value="#E0717070"/>
                <Setter TargetName="Bg"
                        Property="Fill"
                        Value="{StaticResource MenuItemPressedFill}"/>
                <Setter TargetName="InnerBorder"
                        Property="Stroke"
                        Value="#50747272"/>
            </Trigger>
            <Trigger Property="IsSubmenuOpen"
                     Value="true">
                <Setter TargetName="Bg"
                        Property="Stroke"
                        Value="#E0717070"/>
                <Setter TargetName="Bg"
                        Property="Fill"
                        Value="{StaticResource MenuItemPressedFill}"/>
                <Setter TargetName="InnerBorder"
                        Property="Stroke"
                        Value="#50747272"/>
            </Trigger>
            <Trigger Property="IsEnabled"
                     Value="false">
                <Setter Property="Foreground"
                        Value="#FF9A9A9A"/>
                <Setter TargetName="GlyphPanel"
                        Property="Fill"
                        Value="#848589"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
 
    <!-- Submenu -->
    <ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuItemTemplateKey}"
                     TargetType="{x:Type MenuItem}">
        <Grid SnapsToDevicePixels="true">
            <Rectangle Name="Bg"
                       Fill="{TemplateBinding MenuItem.Background}"
                       Stroke="{TemplateBinding MenuItem.BorderBrush}"
                       StrokeThickness="1"
                       RadiusX="2"
                       RadiusY="2"/>
            <Rectangle x:Name="InnerBorder"
                       Margin="1"
                       RadiusX="2"
                       RadiusY="2"/>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition MinWidth="24"
                                      Width="Auto"
                                      SharedSizeGroup="MenuItemIconColumnGroup"/>
                    <ColumnDefinition Width="4"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="37"/>
                    <ColumnDefinition Width="Auto"
                                      SharedSizeGroup="MenuItemIGTColumnGroup"/>
                    <ColumnDefinition Width="17"/>
                </Grid.ColumnDefinitions>
                <ContentPresenter x:Name="Icon"
                                  Margin="1"
                                  VerticalAlignment="Center"
                                  ContentSource="Icon"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                <Border x:Name="GlyphPanel"
                        Background="#E6EFF4"
                        BorderBrush="#CDD3E6"
                        BorderThickness="1"
                        CornerRadius="3"
                        Margin="1"
                        Visibility="Hidden"
                        Width="22" 
                        Height="22">
                    <Path Name="Glyph"
                          Width="9"
                          Height="11"
                          Fill="#0C12A1"
                          FlowDirection="LeftToRight"
                          Data="{StaticResource Checkmark}"/>
                </Border>
                <ContentPresenter Grid.Column="2"
                                  ContentSource="Header"
                                  Margin="{TemplateBinding MenuItem.Padding}"
                                  RecognizesAccessKey="True"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                <TextBlock Grid.Column="4"
                           Text="{TemplateBinding MenuItem.InputGestureText}"
                           Margin="{TemplateBinding MenuItem.Padding}"/>
            </Grid>
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="Icon"
                     Value="{x:Null}">
                <Setter TargetName="Icon"
                        Property="Visibility"
                        Value="Collapsed"/>
            </Trigger>
            <Trigger Property="IsChecked"
                     Value="true">
                <Setter TargetName="GlyphPanel"
                        Property="Visibility"
                        Value="Visible"/>
                <Setter TargetName="Icon"
                        Property="Visibility"
                        Value="Collapsed"/>
            </Trigger>
            <Trigger Property="IsHighlighted"
                     Value="true">
                <Setter TargetName="Bg"
                        Property="Fill"
                        Value="{StaticResource MenuItemSelectionFill}"/>
                <Setter TargetName="Bg"
                        Property="Stroke"
                        Value="#8071CBF1"/>
                <Setter TargetName="InnerBorder"
                        Property="Stroke"
                        Value="#40FFFFFF"/>
            </Trigger>
            <Trigger Property="IsEnabled"
                     Value="false">
                <Setter Property="Foreground"
                        Value="#FF9A9A9A"/>
                <Setter TargetName="GlyphPanel"
                        Property="Background"
                        Value="#EEE9E9"/>
                <Setter TargetName="GlyphPanel"
                        Property="BorderBrush"
                        Value="#DBD6D6"/>
                <Setter TargetName="Glyph"
                        Property="Fill"
                        Value="#848589"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
    <ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuHeaderTemplateKey}"
                     TargetType="{x:Type MenuItem}">
        <Grid SnapsToDevicePixels="true">
            <Rectangle Name="Bg"
                       Fill="{TemplateBinding MenuItem.Background}"
                       Stroke="{TemplateBinding MenuItem.BorderBrush}"
                       StrokeThickness="1"
                       RadiusX="2"
                       RadiusY="2"/>
            <Rectangle x:Name="InnerBorder"
                       Margin="1"
                       Stroke="Transparent"
                       StrokeThickness="1"
                       RadiusX="2"
                       RadiusY="2"/>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition MinWidth="24"
                                      Width="Auto"
                                      SharedSizeGroup="MenuItemIconColumnGroup"/>
                    <ColumnDefinition Width="4"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="37"/>
                    <ColumnDefinition Width="Auto"
                                      SharedSizeGroup="MenuItemIGTColumnGroup"/>
                    <ColumnDefinition Width="17"/>
                </Grid.ColumnDefinitions>
                <ContentPresenter x:Name="Icon"
                                  Margin="1"
                                  VerticalAlignment="Center"
                                  ContentSource="Icon"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                <Border x:Name="GlyphPanel"
                        Background="#E6EFF4"
                        BorderBrush="#CDD3E6"
                        BorderThickness="1"
                        CornerRadius="3"
                        Margin="1"
                        Visibility="Hidden"
                        Width="22" 
                        Height="22">
                    <Path Name="Glyph"
                          Width="9"
                          Height="11"
                          Fill="#0C12A1"
                          FlowDirection="LeftToRight"
                          Data="{StaticResource Checkmark}"/>
                </Border>
                <ContentPresenter Grid.Column="2"
                                  ContentSource="Header"
                                  Margin="{TemplateBinding MenuItem.Padding}"
                                  RecognizesAccessKey="True"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                <TextBlock Grid.Column="4"
                           Text="{TemplateBinding MenuItem.InputGestureText}"
                           Margin="{TemplateBinding MenuItem.Padding}"
                           Visibility="Collapsed"/>
                <Path Grid.Column="5"
                      VerticalAlignment="Center"
                      Margin="4,0,0,0"
                      Fill="{TemplateBinding MenuItem.Foreground}"
                      Data="{StaticResource RightArrow}"/>
            </Grid>
            <Popup x:Name="PART_Popup"
                   AllowsTransparency="true"
                   Placement="Right"
                   VerticalOffset="-3"
                   HorizontalOffset="-2"
                   IsOpen="{Binding Path=IsSubmenuOpen,RelativeSource={RelativeSource TemplatedParent}}"
                   Focusable="false"
                   PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}">
                <theme:SystemDropShadowChrome Name="Shdw"
                                              Color="Transparent">
                    <ContentControl Name="SubMenuBorder"
                                    Template="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=SubmenuContent}}"
                                    IsTabStop="false">
                        <ScrollViewer Name="SubMenuScrollViewer" CanContentScroll="true" MaxHeight="400"
                                      Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=MenuScrollViewer}}">
                            <Grid RenderOptions.ClearTypeHint="Enabled">
                                <Canvas Height="0" Width="0" HorizontalAlignment="Left" VerticalAlignment="Top">
                                    <Rectangle
                                    Height="{Binding ElementName=SubMenuBorder,Path=ActualHeight}" 
                                    Width="{Binding ElementName=SubMenuBorder,Path=ActualWidth}" 
                                    Fill="{StaticResource SubMenuBackgroundBrush}" />
                                </Canvas>
                                <ItemsPresenter Name="ItemsPresenter" Margin="2"
                                            KeyboardNavigation.TabNavigation="Cycle"
                                            KeyboardNavigation.DirectionalNavigation="Cycle"
                                            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                            Grid.IsSharedSizeScope="true"/>
                            </Grid>
                        </ScrollViewer>
                    </ContentControl>
                </theme:SystemDropShadowChrome>
            </Popup>
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="IsSuspendingPopupAnimation"
                     Value="true">
                <Setter TargetName="PART_Popup"
                        Property="PopupAnimation"
                        Value="None"/>
            </Trigger>
            <Trigger Property="IsHighlighted"
                     Value="true">
                <Setter TargetName="InnerBorder"
                        Property="Stroke"
                        Value="#D1DBF4FF"/>
            </Trigger>
            <Trigger Property="Icon"
                     Value="{x:Null}">
                <Setter TargetName="Icon"
                        Property="Visibility"
                        Value="Collapsed"/>
            </Trigger>
            <Trigger Property="IsChecked"
                     Value="true">
                <Setter TargetName="GlyphPanel"
                        Property="Visibility"
                        Value="Visible"/>
                <Setter TargetName="Icon"
                        Property="Visibility"
                        Value="Collapsed"/>
            </Trigger>
            <Trigger SourceName="PART_Popup"
                     Property="Popup.HasDropShadow"
                     Value="true">
                <Setter TargetName="Shdw"
                        Property="Margin"
                        Value="0,0,5,5"/>
                <Setter TargetName="Shdw"
                        Property="Color"
                        Value="#71000000"/>
            </Trigger>
            <Trigger Property="IsHighlighted"
                     Value="true">
                <Setter TargetName="Bg"
                        Property="Fill"
                        Value="{StaticResource MenuItemSelectionFill}"/>
                <Setter TargetName="Bg"
                        Property="Stroke"
                        Value="#8571CBF1"/>
            </Trigger>
            <Trigger Property="IsEnabled"
                     Value="false">
                <Setter Property="Foreground"
                        Value="#FF9A9A9A"/>
                <Setter TargetName="GlyphPanel"
                        Property="Background"
                        Value="#EEE9E9"/>
                <Setter TargetName="GlyphPanel"
                        Property="BorderBrush"
                        Value="#DBD6D6"/>
                <Setter TargetName="Glyph"
                        Property="Fill"
                        Value="#848589"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
    <Style x:Key="{x:Type MenuItem}"
           TargetType="{x:Type MenuItem}">
        <Setter Property="HorizontalContentAlignment"
                Value="{Binding Path=HorizontalContentAlignment,RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
        <Setter Property="VerticalContentAlignment"
                Value="{Binding Path=VerticalContentAlignment,RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
        <Setter Property="Background"
                Value="Transparent"/>
        <Setter Property="ScrollViewer.PanningMode"
                Value="Both"/>
        <Setter Property="Stylus.IsFlicksEnabled"
                Value="False"/>
        <Setter Property="Template"
                Value="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuItemTemplateKey}}"/>
        <Style.Triggers>
            <Trigger Property="Role"
                     Value="TopLevelHeader">
                <Setter Property="Padding"
                        Value="7,2,8,3"/>
                <Setter Property="Template"
                        Value="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=TopLevelHeaderTemplateKey}}"/>
            </Trigger>
            <Trigger Property="Role"
                     Value="TopLevelItem">
                <Setter Property="Padding"
                        Value="7,2,8,3"/>
                <Setter Property="Template"
                        Value="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=TopLevelItemTemplateKey}}"/>
            </Trigger>
            <Trigger Property="Role"
                     Value="SubmenuHeader">
                <Setter Property="Padding"
                        Value="2,3,2,3"/>
                <Setter Property="Template"
                        Value="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuHeaderTemplateKey}}"/>
            </Trigger>
            <Trigger Property="Role"
                     Value="SubmenuItem">
                <Setter Property="Padding"
                        Value="2,3,2,3"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</ResourceDictionary>

It’s not pretty. I hope MS adds the ability to change the height in future versions as I would rather not write a separate extended control just for this.

Until next time…

This is a bit dated, but the question is about how you get (near) accurate processor information in Windows 2003 with WMI and no external executables.

I wrote this a long time ago for VBS and translated it into C# for my upcoming application: Astronomy. I added some snazzy XAML in there to pimp it out a bit. There is a lot of code here, but that is because a lot needs to be done to format the data into something we want to see.

The desired result:

So, let’s see all that code:

The XAML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<Window x:Class="GetProcessCountLegacy.MainWindow"
        Title="MainWindow" Height="200" Width="525">
 
    <Window.Resources>
 
        <!-- Text Styles -->
        <Style x:Key="ListHeader" TargetType="{x:Type TextBlock}">
            <Setter Property="FontWeight" Value="Bold" />
        </Style>
        <Style x:Key="ListLabel" TargetType="{x:Type TextBlock}">
            <Setter Property="FontWeight" Value="Normal" />
        </Style>
        <Style x:Key="ListValue" TargetType="{x:Type TextBlock}">
            <Setter Property="FontWeight" Value="DemiBold" />
        </Style>
 
        <!-- Border -->
        <Style x:Key="BorderStyle" TargetType="Border">
            <Setter Property="BorderBrush" Value="#999" />
            <Setter Property="BorderThickness" Value="1" />
            <Setter Property="CornerRadius" Value="6" />
            <Setter Property="Padding" Value="16,14,16,16" />
            <Setter Property="Margin" Value="12,6,12,6" />
            <Setter Property="Background">
                <Setter.Value>
                    <LinearGradientBrush StartPoint="0.5, 0" EndPoint="0.5, 1" Opacity="1.0">
                        <GradientStop Color="#FFFFFF" Offset="0" />
                        <GradientStop Color="#FFFFFF" Offset="0.5" />
                        <GradientStop Color="#EDEDED" Offset="1" />
                    </LinearGradientBrush>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
 
    <Grid>
        <!-- Processor Information -->
        <Border Style="{StaticResource BorderStyle}">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="28"/>
                    <RowDefinition Height="22"/>
                    <RowDefinition Height="22"/>
                    <RowDefinition Height="22"/>
                    <RowDefinition Height="22"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="120"/>
                    <ColumnDefinition Width="600"/>
                </Grid.ColumnDefinitions>
 
                <TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
                                       VerticalAlignment="Top"
                                       Text="Processor Information"
                                       Style="{StaticResource ListHeader}" />
 
                <TextBlock Grid.Row="1" Grid.Column="0"
                                       Text="Description:"
                                       Style="{StaticResource ListLabel}" />
                <TextBlock Grid.Row="1" Grid.Column="1"
                                       x:Name="txtDescription"
                                       Style="{StaticResource ListValue}" />
 
                <TextBlock Grid.Row="2" Grid.Column="0"
                                       Text="Core Speed:"
                                       Style="{StaticResource ListLabel}" />
                <TextBlock Grid.Row="2" Grid.Column="1"
                                       x:Name="txtSpeed"
                                       Style="{StaticResource ListValue}" />
 
                <TextBlock Grid.Row="3" Grid.Column="0"
                                       Text="Count:"
                                       Style="{StaticResource ListLabel}" />
                <TextBlock Grid.Row="3" Grid.Column="1"
                                       x:Name="txtCount"
                                       Style="{StaticResource ListValue}" />
 
                <TextBlock Grid.Row="4" Grid.Column="0"
                                       Text="Width:"
                                       Style="{StaticResource ListLabel}" />
                <TextBlock Grid.Row="4" Grid.Column="1"
                                       x:Name="txtWidth"
                                       Style="{StaticResource ListValue}" />
            </Grid>
        </Border>
    </Grid>
</Window>

C#

[Main Code]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    #region Contructors
 
    /// <summary>
    /// Main constructor.
    /// </summary>
    public MainWindow()
    {
        // Initialize UI
        InitializeComponent();
 
        // Get processor information
        GetProcessorInformation();
    }
 
    #endregion
 
    /// <summary>
    /// Retrieve the processor information and add to UI.
    /// </summary>
    private void GetProcessorInformation()
    {
        // Build an options object for the remote connection
        var options = new ConnectionOptions
            {
                Impersonation = ImpersonationLevel.Impersonate,
                EnablePrivileges = true
            };
 
        // Make a connection to a remote computer.
        var ms = new ManagementScope(@"" + "." + @"rootcimv2", options);
 
        // Query
        string query = "SELECT Manufacturer, MaxClockSpeed, AddressWidth, Name, " +
                       "SocketDesignation, ProcessorID, UniqueID FROM Win32_Processor";
 
        // Searcher
        var searcher =
            new ManagementObjectSearcher(ms, new WqlObjectQuery(query));
 
        // Get legacy info
        var processor = GetProcessorInfoLegacy(searcher);
 
        // Processor information
        // Description
        txtDescription.Text = RemoveExcessSpaces(processor.Name);
        if (String.IsNullOrEmpty(txtDescription.Text))
        {
            txtDescription.Text = "{Unknown}";
        }
 
        // Speed
        txtSpeed.Text = GetProcessorSpeed(processor.Speed);
        if (String.IsNullOrEmpty(txtSpeed.Text))
        {
            txtSpeed.Text = "{Unknown}";
        }
 
        // Count
        txtCount.Text = GetProcessorCount(5.2, processor);
        if (String.IsNullOrEmpty(txtCount.Text))
        {
            txtCount.Text = "{Unknown}";
        }
 
        // Width
        txtWidth.Text = "x86-" + processor.Architecture;
        if (String.IsNullOrEmpty(txtWidth.Text))
        {
            txtWidth.Text = "{Unknown}";
        }
    }
 
    /// <summary>
    /// Gets processor information for Windows version 5.x.
    /// </summary>
    /// <param name="searcher"></param>
    /// <returns></returns>
    private static ProcessorInfo GetProcessorInfoLegacy(
        ManagementObjectSearcher searcher)
    {
        // Processor object
        var processor = new ProcessorInfo();
 
        // Descriptors
        var socket = new List<string>();
        var procId = new List<string>();
        var uniqueId = new List<string>();
 
        // Get data
        ManagementObjectCollection colItems = searcher.Get();
 
        try
        {
            // Evaluate data
            foreach (ManagementBaseObject objItem in colItems)
            {
                // Manufacturer
                if (objItem["Manufacturer"] != null)
                {
                    processor.Manufacturer = objItem["Manufacturer"].ToString();
                }
 
                // Speed
                if (objItem["MaxClockSpeed"] != null)
                {
                    processor.Speed = Convert.ToInt32(objItem["MaxClockSpeed"]);
                }
 
                // Architecture
                if (objItem["AddressWidth"] != null)
                {
                    processor.Architecture = objItem["AddressWidth"].ToString();
                }
 
                // Socket Designation
                if (objItem["SocketDesignation"] != null)
                {
                    processor.SocketDesignation =
                        objItem["SocketDesignation"].ToString();
                    socket.Add(processor.SocketDesignation);
                }
 
                // Name
                if (objItem["Name"] != null)
                {
                    processor.Name = objItem["Name"].ToString();
                }
 
                // ProcessorID
                if (objItem["ProcessorID"] != null)
                {
                    processor.ProcessorID = objItem["ProcessorID"].ToString();
                    procId.Add(processor.ProcessorID);
                }
                else
                {
                    procId.Add("");
                }
 
                // UniqueID
                if (objItem["UniqueID"] != null)
                {
                    processor.UniqueID = objItem["UniqueID"].ToString();
                    uniqueId.Add(processor.UniqueID);
                }
                else
                {
                    uniqueId.Add("");
                }
            }
 
            // Logical count
            int totalProcCount = colItems.Count;
            processor.LogicalProcessors = totalProcCount;
 
            // Cores
            GetLegacyCoreCount(socket, procId, uniqueId, ref processor);
 
            // Get #of possible sockets
            if ((processor.Cores > 0) && (processor.LogicalProcessors > 0))
            {
                int result = (processor.LogicalProcessors / processor.Cores);
                processor.Count = result;
            }
        }
        catch
        {
        }
        finally
        {
            colItems.Dispose();
        }
 
        // return
        return processor;
    }
 
    /// <summary>
    /// Gets the number of processor cores for Windows version 5.x.
    /// </summary>
    /// <param name="socket"></param>
    /// <param name="procId"></param>
    /// <param name="uniqueId"></param>
    /// <param name="processor"></param>
    private static void GetLegacyCoreCount(List<string> socket, List<string> procId,
                                           List<string> uniqueId, ref ProcessorInfo processor)
    {
        int totalProcessors = 0;
 
        // Processor marker
 
        // Check Socket Designation
        for (int i = 0; i < socket.Count; i++)
        {
            // Start with the assumption this is unique.
            bool isUnique = true;
 
            // Check for Redundancies
            for (int j = i + 1; j < socket.Count; j++)
            {
                if (socket[i] == socket[j])
                {
                    isUnique = false;
                    break;
                }
            }
 
            // If Redundant Unique ID's Exist
            for (int j = i + 1; j < socket.Count; j++)
            {
                if ((uniqueId[i] != "") && (uniqueId[j] != "") &&
                    (uniqueId[i] != uniqueId[j]))
                {
                    isUnique = true;
                    break;
                }
            }
 
            // Check for NULL ProcessorID
            if (procId[i].Trim() == "0000000000000000")
            {
                isUnique = false;
            }
 
            // Calculate Total
            if (isUnique)
            {
                totalProcessors++;
            }
        }
 
        // Get Cores
        int result = (processor.LogicalProcessors / totalProcessors);
        processor.Cores = result;
    }
 
    /// <summary>
    /// Retrieves the Processor speed in human-readable format.
    /// </summary>
    /// <param name="speed"></param>
    /// <returns></returns>
    public static string GetProcessorSpeed(int speed)
    {
        string result = string.Empty;
 
        if (speed.ToString().Length >= 4)
        {
            double dSpeed = Convert.ToDouble(speed) / 1000;
            result = dSpeed.ToString("0.00") + " GHz";
        }
        else
        {
            result = speed.ToString() + " MHz";
        }
 
        // Return
        return result;
    }
 
    /// <summary>
    /// Returns the processor count in a human-readable format.
    /// </summary>
    /// <param name="oSver"></param>
    /// <param name="proc"></param>
    /// <returns></returns>
    public static string GetProcessorCount(double oSver, ProcessorInfo proc)
    {
        string result = "";
 
        int physical = proc.Count;
        int cores = proc.Cores;
 
        // Manufacturer
        string hyper = "HyperThreading";
        if (proc.Manufacturer.ToLower().IndexOf("intel") == -1)
        {
            hyper = "HyperTransport";
        }
 
        // Processor count
        string physDesc = physical.ToString() + " processor(s)";
 
        // Cores
        string coreDesc = string.Empty;
 
        // Current
        if (oSver >= 6)
        {
            coreDesc = cores.ToString() + " core(s)";
            if (proc.IsHyperThreaded)
            {
                coreDesc += " w/ HyperThreading";
            }
        }
 
            // Legacy
        else
        {
            coreDesc = cores.ToString() + " core(s)";
 
            if ((cores / physical) == 2)
            {
                // Intel
                if (proc.Manufacturer.ToLower().IndexOf("intel") != -1)
                {
                    coreDesc += " (or " + physical + " core(s) w/ " + hyper + ")";
                }
            }
        }
 
        result = physDesc + " (" + coreDesc + ")";
 
        // Return
        return result;
    }
 
    /// <summary>
    /// Removes extra spaces between words.
    /// </summary>
    /// <param name="procDesc"></param>
    /// <returns></returns>
    public static string RemoveExcessSpaces(string procDesc)
    {
        string result = "";
 
        // Evaluate
        string[] desc = procDesc.Split((char)32);
 
        // Name
        for (int i = 0; i < desc.Length; i++)
        {
            if (desc[i].Trim() != "")
            {
                result += desc[i] + " ";
            }
        }
 
        // Return
        return result;
    }
}

[ProcessorInfo class]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/// <summary>
/// Information relevant to the physical processors.
/// </summary>
public class ProcessorInfo
{
    #region Properties
 
    /// <summary>
    /// Win32_Processor: AddressWidth: On a 32-bit operating system, the value is 32
    /// and on a 64-bit operating system it is 64.
    /// </summary>
    public string Architecture { get; set; }
 
    /// <summary>
    /// Win32_Processor: NumberOfCores: Number of cores for the current instance of
    /// the processor.
    /// </summary>
    public int Cores { get; set; }
 
    /// <summary>
    /// Total processor count.
    /// </summary>
    public int Count { get; set; }
 
    /// <summary>
    /// Determines whether processors support Hyper-Threading or Hyper-Transport.
    /// </summary>
    public bool IsHyperThreaded { get; set; }
 
    /// <summary>
    /// Win32_Processor: NumberOfLogicalProcessors: Number of logical processors for
    /// the current instance of the processor.
    /// </summary>
    public int LogicalProcessors { get; set; }
 
    /// <summary>
    /// Win32_Processor: Manufacturer: Name of the processor manufacturer.
    /// </summary>
    public string Manufacturer { get; set; }
 
    /// <summary>
    /// Win32_Processor: Name: Label by which the object is known.
    /// </summary>
    public string Name { get; set; }
 
    /// <summary>
    /// Win32_Processor: ProcessorID: Processor information that describes the
    /// processor features.
    /// </summary>
    public string ProcessorID { get; set; }
 
    /// <summary>
    /// Win32_Processor: SocketDesignation: Type of chip socket used on the circuit.
    /// </summary>
    public string SocketDesignation { get; set; }
 
    /// <summary>
    /// Win32_Processor: MaxClockSpeed: Detected processor speed.
    /// </summary>
    public int Speed { get; set; }
 
    /// <summary>
    /// Win32_Processor: UniqueID: Globally unique identifier for the processor.
    /// </summary>
    public string UniqueID { get; set; }
 
    #endregion
 
    #region Constructors
 
    public ProcessorInfo()
    {
        Architecture = "x86";
    }
 
    #endregion
}

Quite often I see people either at work or on forums asking why they can’t see .Net Framework 3.5 in IIS. Then, someone of much experience will tell them to use 2.0 with no further explanation since in most cases the person asking the question just wants it to work. Well, I want you to know the answer, so let me explain that.

In .Net there are 2 versions of consideration: the CLR and the Framework. A .NET CLR can contain several .Net frameworks, since .Net CLR’s are incremental versions (contain content from the earlier version and build upon them like a foundation). To get a visual idea of how this is laid out:

.NET Framework Versions and Dependencies

As you can see, there is not necessarily a new CLR every time a new Framework version is released. In many cases, you can consider the 3.5 framework to be a massive service release update to 2.0.

When configuring IIS, remember to look to the CLR version number and you will be okay. If you can’t find those numbers then you need to check that the right frameworks are installed.

The link posted above from Microsoft lays it all out in nice charts which include the versions of Windows these frameworks are included by default.

That’s all for now…