UIスレッドではないスレッドのテキストボックスを更新する必要があります。
Thread chatRefreshTimer;
void StartChat()
{
chatRefreshTimer = new Thread(new ThreadStart(ChatRefresh));
chatRefreshTimer.Start();
}
void ChatRefresh()
{
conn = new MySqlConnection("Server=...; Database=...; Uid=...; Password=...;");
ds.Clear();
da.SelectCommand = conn.CreateCommand();
da.SelectCommand.CommandText = "select * from chatmessagetbl";
da.SelectCommand.CommandType = CommandType.Text;
while (true)
{
da.Fill(ds, "chatmessagetbl");
textBlockChatArea.Text = "";
foreach (DataRow item in ds.Tables["chatmessagetbl"].Rows)
{
//This has to be done on the thread that owns the textbox.
textBlockChatArea.Dispatcher.BeginInvoke(new Action(() =>
{
textBlockChatArea.Text += item["username"].ToString() + ": " + item["message"].ToString() + "\n";
}));
}
Thread.Sleep(TimeSpan.FromSeconds(1));
}
conn.Dispose();
}
私が提供したコードはあまりきれいではありません。コピーして貼り付けることができる最終的な解決策ではありません(おそらく機能しますが)。正しい方向にあなたを助け、あなたにそれを示すことを試みるだけです。スレッド化で実行できる方法。
編集
テキストボックスの代わりにリストコントロールを使用することについてのコメントで指摘した点を明確にするために、次の疑似プログラムを追加して、意味を説明しました。
// Each row returned from the database will be converted in to one of these.
public class ChatEntry
{
public string UserName { get; set; }
public string Message { get; set; }
public int MessageID { get; set; }
}
// You will need to introduce a MessageID field to your database to make this method work.
public partial class MainWindow : Window
{
public ObservableCollection<ChatEntry> Entries
{
get { return (ObservableCollection<ChatEntry>)GetValue(EntriesProperty); }
set { SetValue(EntriesProperty, value); }
}
public static readonly DependencyProperty EntriesProperty = DependencyProperty.Register("Entries", typeof(ObservableCollection<ChatEntry>), typeof(MainWindow), new UIPropertyMetadata(null));
// This will be used to make sure that only new entries are added to the chat log.
int lastMessageID;
// This will be used to call UpdateEntries every second.
Thread updateThread;
public MainWindow()
{
Entries = new ObservableCollection<ChatEntry>();
InitializeComponent();
updateThread = new Thread(new ThreadStart(UpdateEntries));
updateThread.Start();
}
void UpdateEntries()
{
while (true)
{
// Prepare your query to gather messages from the message table
// with MessageID > lastMessageID.
// This bit needs to be done on the UI dispatcher or it'll cause an exception.
this.Dispatcher.BeginInvoke(new Action(() =>
{
// Each row that came back is a new message and can be added to the collection.
foreach (var row in rows)
{
Entries.Add(new ChatEntry()
{
UserName = (string)row["UserName"],
Message = (string)row["Message"],
});
}
}));
// at this point, the UI will have been upadted with JUST the new entries, no flicker
// no scrolling to the top each second.
// just one more thing, we need to set lastMessageID to be the latest messageID
// so next time UpdateEntries is called it'll only get the new ones that we don't
// have yet.
lastMessageID = Entries.Max(x => x.MessageID);
// Sleep for a second to ease the update speed.
Thread.Sleep(1000);
}
}
}
リストボックスを新しいEntriesプロパティにバインドするには、XAMLで次のようにします。
<Window x:Class="WpfApplication5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
>
<ListView ItemsSource="{Binding Entries}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Margin="0,0,5,0" FontWeight="Bold" />
<TextBlock Text="{Binding Message}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Window>