Leucaruth
Leucaruth

Reputation: 43

Updating Main UI from a usercontrol in WPF with databinding and multithreading

I'm just starting with WPF and still don't have all the facts about it clear.

Right now I'm messing around with data binding and threading to update data and represent it on a time basis. I have a mainwindow with 2 textboxs and an usercontrol defined with a thread that starts to run when I click it. In this thread I make calls to 2 properties set in the main window in which I have binded to the textboxes, one by code and another one by xaml.

The problem is that the data binding made by code works while the one made by xaml only works when I update the textbox directly. I have been wondering why, and with some change I made it to work, but I doubt this solution is "correct".

The code is this

MainWindow.xaml

<Window x:Class="WpfApplication1.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" Loaded="Window_Loaded" xmlns:my="clr-namespace:WpfApplication1.Elements">
    <Grid>
        <TextBox Name="Textbox1" Height="23" HorizontalAlignment="Left" Margin="188,50,0,0" VerticalAlignment="Top" Width="120" Background="White" Text="{Binding Source=my:MainWindow, Path=datavalue}" />
        <TextBox Name="textBox2" Background="White" Height="23" HorizontalAlignment="Left" Margin="188,101,0,0"  Text="{Binding Path=datavalue2, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}} }" VerticalAlignment="Top" Width="120" />

        <Label Content="Label" Height="28" HorizontalAlignment="Left" Margin="202,152,0,0" Name="label1" VerticalAlignment="Top" />
        <my:Test Margin="81,138,346,92" x:Name="test1" />
    </Grid>
</Window>

MainWindow.xaml.cs

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

        public event PropertyChangedEventHandler PropertyChanged;
        private int MydataValue;

        public int datavalue
        {
            get
            {
                return MydataValue;
            }
            set
            {
                MydataValue = value;

                if (PropertyChanged != null)
                {
                    PropertyChanged(this,
                        new PropertyChangedEventArgs(
                        "datavalue"));
                }
             }   
        }

        private int mydatavalue2;

        public int datavalue2
        {
            get
            {
                return mydatavalue2;
            }
            set{
                mydatavalue2 = value;
            }
        }



        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            datavalue2 = 10;
            datavalue = 15;
            Binding bind = new Binding();
            bind.Source = this;
            bind.Mode = BindingMode.TwoWay;
            bind.Path = new PropertyPath("datavalue");
            Textbox1.SetBinding(TextBox.TextProperty, bind);
        } 
    }

}

And this is the usercontrol

test.xaml.cs

namespace WpfApplication1.Elements
{
    /// <summary>
    /// Interaction logic for Test.xaml
    /// </summary>
    public partial class Test : UserControl
    {
        private Thread t;
        private int resolution;
        private Stopwatch sw,sw2;
        private delegate void changeIconDelegate(long ib);
        private ImageBrush b;
        private Boolean end=false;
        private int count = 0;

        public Test()
        {
            InitializeComponent();
            sw = new Stopwatch();
            sw2 = new Stopwatch();
            b = new ImageBrush();
            resolution = 100;
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            b.ImageSource = new BitmapImage(new Uri("pack://application:,,,/DataBinding;component/Images/249137482.png", UriKind.Absolute));
            this.Background = b;
        }

        private void UserControl_MouseUp(object sender, MouseButtonEventArgs e)
        {
            t = new Thread(new ThreadStart(ThreadTask));
            t.Start();
        }

        private void ThreadTask()
        {
            while (!end)
            {
                count = count + 1;
                Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)delegate()
                {
                    Application.Current.Windows.OfType<MainWindow>().First().datavalue = count;
                    Application.Current.Windows.OfType<MainWindow>().First().datavalue2 = count;
                });

                Thread.Sleep((resolution - sw.Elapsed.TotalMilliseconds) > 0 ? TimeSpan.FromMilliseconds(resolution - sw.Elapsed.TotalMilliseconds):TimeSpan.FromMilliseconds(1));
                sw.Reset();
                sw.Start();
            }
        }
    }
}

If I start the application, as soon as I click on the control, textbox1 begins to update, while textbox2 doesnt, but if I alter textbox2 directly, it throws the notification that it changed, so the data binding is doing properly or so I think.

The questions are:

Sorry for the big quantity of code and questions but I'm starting to like WPF and I get somewhat frustrasted when I don't get things working. I seached for similar questions, but only found people having problems when they had to update the control properties from the Main UI, not the other way, like me

Upvotes: 1

Views: 1467

Answers (2)

TonyS
TonyS

Reputation: 238

First don't despair! WPF has a steep learning curve for everyone.

Your approach is correct once the property change has been added. To notify changes in the code you have to raise it to let the UI know. However I find the code a little overcomplicated. I would like to let you know another solution in case you may find it useful

You can put your data in a class and then assign it to the DataContext of your xaml. Once you've done this you are very close to implementing the MVVM pattern which in my humble opinion is the best way to work with WPF. I put some sample code

MainWindow.xaml

<Window x:Class="WpfApplication1.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>
        <TextBox Text="{Binding Value1}"/>
        <TextBox Text="{Binding Value2}"/>
    </StackPanel>
</Window>

MainWindow.xaml.cs

using System.Windows;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            MyClass myClass = new MyClass();
            myClass.Value1 = 1;
            myClass.Value2 = 2;
            DataContext = myClass;
        }
    }

    public class MyClass
    {
        public int Value1 { get; set; }
        public int Value2 { get; set; }
    }
}

For children user controls you can create another class and assign it to the UserControl DataContext. This keeps things together and tidy. Also this avoids the need to do " Application.Current.Windows.OfType().First().datavalue = count;" you can just modify your binded class directly

Hope it helps

Upvotes: 1

Leucaruth
Leucaruth

Reputation: 43

Doing some testing, I tried to add to datavalue2 property the same condition as in datavalue, thats it, firing the property changed event after updating the value

        public int datavalue2
        {
            get
            {
                return mydatavalue2;
            }
            set{
                mydatavalue2 = value;
                if (PropertyChanged != null)
                {
                    PropertyChanged(this,
                        new PropertyChangedEventArgs(
                        "datavalue2"));
                }
            }
        }

Surprisingly it worked althought it shouldnt be necessary to use it. I was wondering if this could make some incompatibilities as far as throwing 2 events if I modified it directly, but it worked fine, only one event throwed for textbox2... Still I do feel uneasy for this approach.

Upvotes: 0

Related Questions