views:

102

answers:

6

I got a thread running every few seconds fetching some data from a db but this is based on the selection on a listbox and on a few checkboxes... can I read the values of these controls without using the GUI thread?

The data is also read whenever one of the controls change, but the data might change in db without warning...hence the thread running every few seconds.

I'm working with wpf C#

A: 

Of course you can, and it is the way to do in case it takes some time to finish the work, so that you don't need to block the GUI thread.

Adrian Shum
uh...so..how is it done?
irco
A: 

In short, no. Controls can only be accessed by the thread that created them.

Prove it for yourself:

<Window x:Class="ReadControlsBackground.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Height="350"
        Width="525">
    <StackPanel>
        <Button x:Name="Start"
                Click="Button_Click"
                Content="Start" />
        <ListBox x:Name="List">
            <ListBoxItem>One</ListBoxItem>
            <ListBoxItem>Two</ListBoxItem>
        </ListBox>
        <CheckBox x:Name="Check" />
    </StackPanel>
</Window>



using System;
using System.Diagnostics;
using System.Threading;
using System.Windows;

namespace ReadControlsBackground {
    /// <summary>
    ///   Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e) {
            Start.IsEnabled = false;
            var t = new Thread(Poll);
            t.Start();
        }

        private void Poll() {
            while (true) {
                Debug.WriteLine(String.Format("List: {0}", List.SelectedValue));
                Debug.WriteLine(String.Format("Check: {0}", Check.IsChecked));
                Thread.Sleep(5000);
            }
        }
    }
}

Do something like this instead:

private void Poll() {
    while (true) {
        var selected = (String) Dispatcher.Invoke((Func<String>) (() => (List.SelectedValue ?? "?").ToString()));
        var isChecked = (Boolean?) Dispatcher.Invoke((Func<Boolean?>) (() => Check.IsChecked));
        Debug.WriteLine(String.Format("List: {0}", selected));
        Debug.WriteLine(String.Format("Check: {0}", isChecked));
        Thread.Sleep(5000);
    }
}
Wayne
Yes I tried it, which was mainly why I was asking, I was hoping there was a way as it is just a read I wanted to find out if there was a way to implement something like a readerWriterLock like I think it should be...after all reads shouldnt be mutually exclusive
irco
The only members of Control that are safe to call from a thread that is not the thread that owns the control's window handle are BeginInvoke, Invoke and EndInvoke, which marshal the call to the thread that does own the handle. [See update above]
Wayne
thanks, I appreciate your help i am already doing something along those lines except i'd throw an anonymous function within what is your Poll function and let the window's dispatcher fetch the data. I didn't know I could read the args like that though, thanks again.
irco
A: 

Hello,

If you want to access the GUI from another thread. Then you will have to marshal the call to the GUI thread.

I don't have the exact details as I have not done C# for a very long time.

If I manage to find something I will write back with some code snippet.

robUK
i'd appreciate that...even if it is in vb or just good old pseudocode.is it through a readerWriterLock?
irco
Actually, I didn't know you was using WPF. I am not sure that it would be the same.
robUK
A: 

Don't use a thread, use a timer. Then you're already in the UI context thread when the timer event fires.

Eric Falsken
This will block the UI while the (assumed synchronous) database call is in flight.
Wayne
the purpose of having a different thread is to allow the GUI to be usable as the DB read is performed as Wayne pointed out, that would block the gui and render the thread worthless
irco
He should start his DB work in a newly spawned thread, and leave the UI and Splash Form created by the originating thread, using either a Timer to check for the first thread's completion, or Marshal a call from the DB thread back to the UI thread to indicate his completion. The method you're looking for is Control.(Begin)Invoke to invoke a method on the control in the UI thread.
Eric Falsken
+1  A: 

Try something like this:

string textBoxData = string.Empty;

/* Blocks current thread until the UI
 * thread executes the anonymous method */
Dispatcher.Invoke((Action) delegate
{
    textBoxData = txtBox.Text;
});

/* Do more work */

You may need to change "Dispatcher.Invoke" to "someWpfControl.Dispatcher"

Jeremiah Morrill
well that is using the GUI thread, which is kind of what i was trying to avoid, but yeah I Was aware of being able to invoke using the control's dispatcher.
irco
Shouldn't you be checking InvokeRequired before doing the invoke?
Martin Clarke
+1  A: 

This problem becomes trivial if you do it the other way around. Have the UI thread read whatever values are required, then let it start the thread, passing those values. ThreadPool.QueueUserWorkItem is ideal for this.

Hans Passant
yeah i know i can use the gui thread, even with a simple anonymous function for reading and then let the thread do its think....but I was asking because the thread keeps running constantly and i wanted to avoid using the gui thread
irco
Well, you can't. Now there's perhaps little point left in having a thread running constantly? You can't just have a thread Invoke to the UI thread and read values while the user is editing them.
Hans Passant