因此,我正在开发一个简单的WPF应用程序,该应用程序应从LAN上“专用页面”中读取信息,并更有效地输出它们。因此,现在我尝试在选择要从中获取数据的ip后使标签内容自动更新。该计划基本上是每分钟左右刷新页面以更新输出。 问题是我尝试了很多有关多线程的解决方案,但仍然遇到相同的错误:InvalidOperationException:调用线程无法访问此对象,因为其他线程拥有它。 基本上,我从中了解到的是mainWindows线程拥有标签的内容,因此从所述文件/字符串读取后,我无法更新它们。
这是我的xaml窗口:
<Window x:Class="PartCountBello.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PartCountBello"
mc:Ignorable="d"
Title="Controllo Macchine" Height="337.42" Width="366.586"
ResizeMode="CanMinimize">
<Grid Margin="0,10,2,12">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="0*"/>
</Grid.ColumnDefinitions>
<ComboBox x:Name="cmbNomiMacchine" HorizontalAlignment="Left"
Margin="44,74,0,0" VerticalAlignment="Top" Width="129" Height="22"
SelectionChanged="cmbNomiMacchine_SelectionChanged"/>
<Label x:Name="lblNomeDato" Content="PartCount :"
HorizontalAlignment="Left" Margin="44,162,0,0" VerticalAlignment="Top"
Height="26" Width="129"/>
<Label x:Name="lblPartCount" Content="" HorizontalAlignment="Left"
VerticalAlignment="Top" Margin="178,162,0,0" Height="26" Width="144"
RenderTransformOrigin="0.071,0.731"/>
<Label x:Name="lblSelectInfos" Content="Selezionare macchina"
HorizontalAlignment="Left" Margin="44,43,0,0" VerticalAlignment="Top"
Width="129" Height="26"/>
<Label x:Name="lblLavoro" Content="Mansione : "
HorizontalAlignment="Left" Margin="44,210,0,0" VerticalAlignment="Top"
Width="129"/>
<Label x:Name="lblMansione" Content="" HorizontalAlignment="Left"
Margin="178,210,0,0" VerticalAlignment="Top" Width="144"/>
<Button x:Name="btnRefresh" Content="Aggiorna"
HorizontalAlignment="Left" Margin="247,74,0,0" VerticalAlignment="Top"
Width="75" Height="22" Click="btnRefresh_Click"/>
<Label x:Name="lblMoreInfos" Content="" HorizontalAlignment="Left"
Margin="10,241,0,0" VerticalAlignment="Top" Width="339" Height="35"/>
</Grid>
</Window>
这是我的主要Windows代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace PartCountBello
{
/// <summary>
/// Logica di interazione per MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
List<string> datas = new List<string> { }; //list that contains machines names and relatives ips. each ip is in the previous index...
//respect to the machine.
public MainWindow()
{
InitializeComponent();
try
{
ReadFile rf = new ReadFile(); //creates the file reader we 're using to...
datas = rf.getListFromFile("MyIPsFile"); //get all our datas from our file.. Not posting the real file name here cuz you never know. still contains only the ips to connect to.
for(int i = 2;i<=datas.Count-1;i+=3)
{
cmbNomiMacchine.Items.Add(datas[i]);
}
}
catch (Exception ex)
{
lblMoreInfos.Content = ex.Message;
}
finally
{
}
}
/// <summary>
/// when the selection is changed to an item it will update the labels contents through a method in the class activityChecker.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void cmbNomiMacchine_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
updateLabelsContent();
}
/// <summary>
/// gets the index the item selected in the combobox has in the file to pass it to the fileread and get the ip.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
private int getSelectedItemIndex(int index)
{
int FIRST_REAL_INDEX = 2;
int realIndex;
if (index == 0)
return 0;
else
return realIndex=FIRST_REAL_INDEX+index*2;
}
/// <summary>
/// checks if the machine we are trying to connect to is on, if so updates the dedicated lables' content, else prints a simple message down below.
/// </summary>
private void updateLabelsContent()
{
string toShow;//the auxiliary string we are going to use to output on labels.
ActivityChecker ac = new ActivityChecker();
lblMoreInfos.Content = "";
if (getSelectedItemIndex(cmbNomiMacchine.SelectedIndex) != 0)
{
if (PingHost(datas[getSelectedItemIndex(cmbNomiMacchine.SelectedIndex) - 1]))
{
toShow = ac.getPartCount(datas[getSelectedItemIndex(cmbNomiMacchine.SelectedIndex)]);
lblPartCount.Content = toShow;
toShow = ac.getJob(datas[getSelectedItemIndex(cmbNomiMacchine.SelectedIndex)]);
lblMansione.Content = toShow;
}
else
{
lblMoreInfos.Content = "La macchina è al momento spenta.";
}
}
else
{
lblMansione.Content = "";
lblPartCount.Content = "";
}
}
/// <summary>
/// updates the content of the machine dedicated labels.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnRefresh_Click(object sender, RoutedEventArgs e)
{
updateLabelsContent();
}
/// <summary>
/// allows to verify if the machine we are trying to connect to is on before we actually try to, avoids some freezes.
/// </summary>
/// <param name="nameOrAddress"></param>
/// <returns></returns>
public static bool PingHost(string nameOrAddress)
{
bool pingable = false;
Ping pinger = new Ping();
try
{
PingReply reply = pinger.Send(nameOrAddress);
pingable = reply.Status == IPStatus.Success;
}
catch (PingException)
{
// Discard PingExceptions and return false;
}
return pingable;
}
}
}
答案 0 :(得分:1)
好的,我找到了解决方案。
将此添加到mainwindows.cs:
/// <summary>
/// private method that manages the thread for the automatic update.
/// </summary>
private void updateLabelsContentThread()
{
while(true)
{
Thread.Sleep(TimeSpan.FromSeconds(10));
Dispatcher.Invoke(new Action(() => { updateLabelsContent(); }));
}
}
并将机器选择的事件方法更改为此。
/// <summary>
/// when the selection is changed to an item it will update the labels contents through a method in the class activityChecker.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void cmbNomiMacchine_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
updateLabelsContent();
Thread t = new Thread(updateLabelsContentThread);
t.Start();
}
对我来说效果很好,请留在这里,以防它对某人有所帮助。
答案 1 :(得分:0)
我不提倡这种类似于WinForms的方法,可能有使用绑定的更好的解决方案,但它需要进行很多更改。如果现在需要这种方式:
public partial class MainWindow : Window
{
....
private readonly SynchronizationContext uiContext;
public MainWindow()
{
InitializeComponent();
//controls created on a specific thread can only be modified by that thread.
//(99.99999%) of the time controls are created and modified on the UI thread
//SynchronizationContext provides a way to capture and delegate work to the UI thread (it provides more functionality than this, but in your case this is what interests you)
//We capture the UI Synchronization Context so that we can queue items for execution to the UI thread. We know this is the UI thread because we are in the constructor for our main window
uiContext = SynchronizationContext.Current;
....
}
private void cmbNomiMacchine_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
UpdateOnUIThread();
}
...............
///I Chose to write another method for clarity, feel free to rework the code anyway you like. Ideally you want to only delegate short work to the UI thread (say textbox.Text = "". This is just here to show the concept
private void UpdateOnUIThread()
{
//Post is asynchronous so will give controll back immediately. If you want synchronous operation, use Send
uiContext.Post(new SendOrPostCallback((o) => { updateLabelsContent(); }), null);
}
..............
}
}