I'm having trouble having something update in my WPF UI. The same part of the code that's throwing the exception works just fine in the other function. Now, for some reason, I can't figure out it's not only throwing an exception, it is also not updating like I want it to. I want the UI elements in the OnCPUDetEvent()
function to update based off of the timer I have set up.
Here's my code:
using System;
using System.Collections.Generic;
using System.Management.Instrumentation;
using System.Management;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Runtime.Serialization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace HWDetCS
{
/// <summary>
/// Interaction logic for CPUBase.xaml
/// </summary>
public partial class CPUBase : Page
{
// lists are better than arrays, fite me!
public List<string> names = new List<string>();
public List<string> values = new List<string>();
public int i = 0;
// Set up a timer to be enabled later
public Timer CPUDetRefreshTimer;
public CPUBase()
{
// Auto generated stuff, don't touch!
InitializeComponent();
// Actually run all the detection stuff
CPUDet();
// Start up the Timer, and get it ready
CPUDetRefreshTimer = new Timer();
CPUDetRefreshTimer.AutoReset = true;
CPUDetRefreshTimer.Interval = 500;
CPUDetRefreshTimer.Enabled = true;
CPUDetRefreshTimer.Elapsed += OnCPUDetEvent;
}
// This thing does all the work
public void CPUDet()
{
// Get the CPU Management class, this makes it the CPU we get info off of rather than nothing, because if it wasnt set to the CPU, it would error and break and cry a lot... dont change it.
ManagementClass CPUClass = new ManagementClass("Win32_Processor");
CPUClass.Options.UseAmendedQualifiers = true;
// Set up a data collection to get the data off of, this and the next thing SHOULD NEVER BE IN A LOOP! IT WILL BREAK YOUR CPU LIKE A BALLOON!
PropertyDataCollection dataCollection = CPUClass.Properties;
// Get the instance of the class, for some reason this is required to work, dont touch AND DONT PUT IT IN A LOOP WHY CANT YOU LISTEN!?
ManagementObjectCollection instanceCollection = CPUClass.GetInstances();
// This is a loop, its very fragile, dont touch it, it gets the list of data we are collecting
foreach (PropertyData property in dataCollection)
{
// adds the names into one nice readable-ish list!
names.Add(property.Name);
// loop through all the instances and grabs the actual data off of it
foreach (ManagementObject instance in instanceCollection)
{
// makes sure we dont get null reference errors, I HATE THOSE SO MUCH! I KNOW ITS NULL JUST SHUT UP!
if (instance.Properties[property.Name.ToString()].Value == null)
{
// if its null, dont add the actual property data, INSTEAD, add a string that says null so we know not to mess with it
values.Add("null");
}
else
{
// otherwise, go right ahead
values.Add(instance.Properties[property.Name.ToString()].Value.ToString());
}
}
// counting....
i++;
}
// Debug stuff, dont release uncommented!
// TODO: COMMENT THIS OUT!
for (int x = 0; x < names.Count - 1; x++)
{
Console.WriteLine(x.ToString());
Console.WriteLine(names[x]);
Console.WriteLine(values[x]);
}
// Get the name
CPUNameText.Content = values[29];
// Get the manufacturer
CPUManuText.Content = values[27];
// Get the number of CORES (NOT THREADS!)
CPUCoreCountText.Content = values[30];
// Get the Family
CPUFamilyText.Content = values[18];
}
public void OnCPUDetEvent(Object obj, ElapsedEventArgs args)
{
//Console.WriteLine("Event Fire!");
// Get the current clock speed
CPUClockSpeedText.Content = values[10] + "MHz";
// Get the current Voltage
CPUCVoltageText.Content = (Convert.ToDouble(values[11]) / 10).ToString() + " Volts";
}
}
}
Here is the XAML for the actual UI Page:
<Page x:Class="HWDetCS.CPUBase"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:HWDetCS"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Title="CPUBase" MinWidth="1280" MinHeight="720">
<Grid Background="Gray">
<DockPanel>
<UniformGrid Rows="6" Columns="2">
<Label x:Name="CPUNameLabel" Content="CPU Name:" FontSize="36" FontWeight="Bold" Foreground="#FF007ACC" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
<Label x:Name="CPUNameText" Content="Label" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="24" FontWeight="Bold" Foreground="#FF007ACC"/>
<Label x:Name="CPUManuLabel" Content="CPU Manufacturer:" FontSize="36" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontWeight="Bold" Foreground="#FF007ACC"/>
<Label x:Name="CPUManuText" Content="Label" Foreground="#FF007ACC" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="24" FontWeight="Bold"/>
<Label x:Name="CPUClockSpeedLabel" Content="CPU Clock Speed:" Foreground="#FF007ACC" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="36" FontWeight="Bold"/>
<Label x:Name="CPUClockSpeedText" Content="Label" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="24" FontWeight="Bold" Foreground="#FF007ACC"/>
<Label x:Name="CPUCoreCountLabel" Content="CPU Core Count:" Foreground="#FF007ACC" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="36" FontWeight="Bold"/>
<Label x:Name="CPUCoreCountText" Content="Label" Foreground="#FF007ACC" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="24" FontWeight="Bold"/>
<Label x:Name="CPUFamilyLabel" Content="CPU Family:" FontSize="36" FontWeight="Bold" Foreground="#FF007ACC" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
<Label x:Name="CPUFamilyText" Content="Label" Foreground="#FF007ACC" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="24" FontWeight="Bold"/>
<Label x:Name="CPUCVoltageLabel" Content="CPU Current Voltage:" FontSize="36" FontWeight="Bold" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Foreground="#FF007ACC"/>
<Label x:Name="CPUCVoltageText" Content="Label" Foreground="#FF007ACC" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="24" FontWeight="Bold"/>
</UniformGrid>
</DockPanel>
</Grid>
</Page>
The error you got is the result of an "access denied" error. Your function starts its life in a different thread than the GUI itself, as a result it is not allowed to change GUI elements.
It is described in this link: A method running on a non-UI thread updates the UI
Above link gives methods to run away from the problem. However, there is no single solution for your problem. The following is just one of them (there is another answer down the page). It does step-by-step changes you need to apply. (EDIT: I have added another solution in the next answer)
Add using System.ComponentModel;
to your headers. This is to use binds with changing properties. If needed, add the reference dll file.
Now use this heading for your class :
public partial class CPUBase : Page,INotifyPropertyChanged
Next step is adding notifiers. The code is used as is for .NET v4.0, consult to INotifyPropertyChanged Interface how to use for other versions.
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
now create two property field to hold your changing values. Consult the above link for other versions.
string speed;
public string Speed
{
get
{
return speed;
}
set
{
speed= value;
NotifyPropertyChanged(nameof(Speed));
}
}
string volt;
public string Volt
{
get
{
return volt;
}
set
{
volt = value;
NotifyPropertyChanged(nameof(Volt));
}
}
The rest is much easier now. just change these properties inside your function (extra i
is to show it is working as the values don't show immediate changes) (Edit: Additions work but I don't think the code gets active values
, I tried to decrease CPU freq, and result didnt change)
Speed = values[10] + "MHz"+i;
Volt = (Convert.ToDouble(values[11]) / 10).ToString() + " Volts"+i;
i++;
Now give your page a name and update labels by bindings to these properties.
<Page x:Name="CPUBaseMain ...
...
<Label x:Name="CPUClockSpeedText" Content="{Binding Speed, ElementName=CPUBaseMain}" ...
<Label x:Name="CPUCVoltageText" Content="{Binding Volt, ElementName=CPUBaseMain}" ...