【问题标题】:Updating Main UI from a usercontrol in WPF with databinding and multithreading使用数据绑定和多线程从 WPF 中的用户控件更新主 UI
【发布时间】:2013-05-26 02:41:06
【问题描述】:

我刚刚开始使用 WPF,但仍然没有清楚地了解它的所有事实。

现在我正在搞乱数据绑定和线程来更新数据并按时间表示它。我有一个带有 2 个文本框的主窗口和一个用一个线程定义的用户控件,当我单击它时该线程开始运行。在这个线程中,我调用了在我绑定到文本框的主窗口中设置的 2 个属性,一个通过代码,另一个通过 xaml。

问题是代码生成的数据绑定有效,而 xaml 生成的数据绑定仅在我直接更新文本框时才有效。我一直想知道为什么,并通过一些更改使其工作,但我怀疑这个解决方案是“正确的”。

代码是这样的

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);
        } 
    }

}

这是用户控件

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();
            }
        }
    }
}

如果我启动应用程序,只要我点击控件,textbox1 就会开始更新,而 textbox2 不会,但是如果我直接更改 textbox2,它会抛出它已更改的通知,因此数据绑定正在正常进行或所以我认为。

问题是:

  • 为什么如果直接修改而不是在用户控件的线程调用时它可以工作?我认为它与 xaml 文件中 textbox2 的声明有关,因为它搜索祖先,而 usercontrol 是一个孩子,但我不知道如何解决这个问题。

  • propertychanged 事件适用于类的每个属性,但如果进行多线程处理,它可能会采用不正确的值并将一个事件误认为另一个事件?

  • 我确定我没有正确使用 WPF 的规范,这可能会以某种方式影响代码,例如从用户控件调用主窗口时:S

对于大量的代码和问题,我感到很抱歉,但我开始喜欢 WPF,当我没有让事情正常工作时,我感到有些沮丧。我搜索了类似的问题,但只发现人们在必须从主 UI 更新控件属性时遇到问题,而不是像我这样的其他方式

【问题讨论】:

    标签: c# wpf multithreading data-binding


    【解决方案1】:

    首先不要绝望! WPF 对每个人都有一个陡峭的学习曲线。

    添加属性更改后,您的方法是正确的。要通知代码中的更改,您必须提出它以让 UI 知道。但是我发现代码有点过于复杂。我想让您知道另一种解决方案,以防您发现它有用

    您可以将数据放在一个类中,然后将其分配给 xaml 的 DataContext。完成此操作后,您就非常接近实现 MVVM 模式,在我看来,这是使用 WPF 的最佳方式。我放了一些示例代码

    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; }
        }
    }
    

    对于子用户控件,您可以创建另一个类并将其分配给 UserControl DataContext。这使事情保持在一起和整洁。这也避免了执行“Application.Current.Windows.OfType().First().datavalue = count;”的需要你可以直接修改你的绑定类

    希望对你有帮助

    【讨论】:

    • 如果对 Value1Value2 的更改将反映在 UI 中,您仍然需要为 MyClass 实现 INotifyPropertyChanged - 这是问题所要解决的具体问题关于。
    • 然而,我非常感谢您的两个回答,因为它帮助我改进了我使用 WPF 的方式,因为 MVVM 是这方面的基础 :)
    【解决方案2】:

    做一些测试,我尝试在 datavalue2 属性中添加与 datavalue 相同的条件,就是这样,在更新值后触发属性更改事件

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

    令人惊讶的是,尽管没有必要使用它,但它确实有效。我想知道如果我直接修改它是否会引发一些不兼容,就抛出 2 个事件而言,但它工作正常,只为 textbox2 抛出一个事件......我仍然对这种方法感到不安。

    【讨论】:

    • 这一点也不奇怪。引发 PropertyChanged 事件会通知消费者 singlespecific 属性已更改,并且只会更新该属性的绑定。您的解决方案完全正确。
    • 是的,问题是我已经在 xaml 中拥有了路径和 updatesourcetrigger,如果直接从窗口修改,它可以完美地工作,而不必自己触发事件。如果我从用户控件更新它,除非我手动触发事件,否则它不会做任何事情
    • 是的:因为当您的文本框控件更改时,它会触发一个事件,以便绑定知道要更新。当您的属性发生变化时,您没有触发任何事件,因此绑定不知道要更新。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-28
    相关资源
    最近更新 更多