Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/278.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 是否有限制WPF DataTrigger事件的解决方法?_C#_Wpf_Events_Data Binding - Fatal编程技术网

C# 是否有限制WPF DataTrigger事件的解决方法?

C# 是否有限制WPF DataTrigger事件的解决方法?,c#,wpf,events,data-binding,C#,Wpf,Events,Data Binding,从那时起,我最终发现WPF确实限制了它的“绑定事件” 下面的代码演示了该行为:单击按钮启动一项任务,循环次数为100次,每2ms更新一个属性。绑定到此属性的控件统计触发DataContextChanged的次数 在我的机器上,如果我单击按钮5次,输出如下: Iterations: 100, Count: 86 Iterations: 100, Count: 87 Iterations: 100, Count: 85 Iterations: 100, Count: 89 Iterations: 1

从那时起,我最终发现WPF确实限制了它的“绑定事件”

下面的代码演示了该行为:单击按钮启动一项任务,循环次数为100次,每2ms更新一个属性。绑定到此属性的控件统计触发
DataContextChanged
的次数

在我的机器上,如果我单击按钮5次,输出如下:

Iterations: 100, Count: 86
Iterations: 100, Count: 87
Iterations: 100, Count: 85
Iterations: 100, Count: 89
Iterations: 100, Count: 90
我最初的问题(我想,请看上面的链接)是动画只触发一次。然后我发现,没有响应的不是动画,而是
DataTrigger
。然后我研究了
DataContextChanged
(参见代码)的行为,现在假设基本上WPF限制了所有事件

我只想在XAML中定义动画,这意味着我必须依赖于
DataTrigger
像“始终”一样“正确”触发。显然没有。为了实现我最初想要的动画,我必须实现我自己的事件(不会被限制)并从代码中触发动画吗

编辑:

<UserControl x:Class="AnimationTests.AnimationTestControl"
             x:Name="self"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:AnimationTests"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid DataContext="{Binding ElementName=self}">
        <StackPanel Orientation="Horizontal">
            <Button Content="Toggle Border" Click="ButtonToggle_Click" />

            <Border x:Name="TestBorder" DataContext="{Binding ElementName=self, Path=TestBorderItem.MyState}"/>
        </StackPanel>
    </Grid>
</UserControl>
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace AnimationTests
{

    public class TestItem : INotifyPropertyChanged
    {
        private int _myState;
        public int MyState
        {
            get => _myState;
            set { _myState = value; OnPropertyChanged(); }
        }
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public partial class AnimationTestControl : UserControl
    {
        public AnimationTestControl()
        {
            InitializeComponent();
        }

        public TestItem TestBorderItem { get; } = new TestItem();

        private void ButtonToggle_Click(object sender, RoutedEventArgs e)
        {
            Task.Run(() =>
            {
                var cnt = 0;
                var max = 100;
                void handler(object s, DependencyPropertyChangedEventArgs args) => cnt++;
                
                TestBorder.DataContextChanged += handler;

                for (int i = 0; i < max; i++)
                {
                    TestBorderItem.MyState = i + 1;
                    Thread.Sleep(2);
                }

                Console.WriteLine($"{max}: {cnt}");

                TestBorder.DataContextChanged -= handler;
            });
        }
    }
}
  • 澄清一下:我不想每2毫秒触发一次动画!仅当设置了特定值时才应触发动画,并且仅在用户交互时才会触发,在我当前的设置中,通常最多每隔几秒钟触发一次
  • 但是:观察到的属性仅在很短的时间内具有该特定值,例如2ms。而且,很明显,由于WPF的限制(如下面的回答所指出的),仅此属性更改就可能丢失
  • 所以,问题仍然是:我必须。。。实现我自己的事件。。。并从代码中触发动画
AnimationTestControl.xaml:

<UserControl x:Class="AnimationTests.AnimationTestControl"
             x:Name="self"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:AnimationTests"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid DataContext="{Binding ElementName=self}">
        <StackPanel Orientation="Horizontal">
            <Button Content="Toggle Border" Click="ButtonToggle_Click" />

            <Border x:Name="TestBorder" DataContext="{Binding ElementName=self, Path=TestBorderItem.MyState}"/>
        </StackPanel>
    </Grid>
</UserControl>
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace AnimationTests
{

    public class TestItem : INotifyPropertyChanged
    {
        private int _myState;
        public int MyState
        {
            get => _myState;
            set { _myState = value; OnPropertyChanged(); }
        }
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public partial class AnimationTestControl : UserControl
    {
        public AnimationTestControl()
        {
            InitializeComponent();
        }

        public TestItem TestBorderItem { get; } = new TestItem();

        private void ButtonToggle_Click(object sender, RoutedEventArgs e)
        {
            Task.Run(() =>
            {
                var cnt = 0;
                var max = 100;
                void handler(object s, DependencyPropertyChangedEventArgs args) => cnt++;
                
                TestBorder.DataContextChanged += handler;

                for (int i = 0; i < max; i++)
                {
                    TestBorderItem.MyState = i + 1;
                    Thread.Sleep(2);
                }

                Console.WriteLine($"{max}: {cnt}");

                TestBorder.DataContextChanged -= handler;
            });
        }
    }
}

AnimationTestControl.xaml.cs:

<UserControl x:Class="AnimationTests.AnimationTestControl"
             x:Name="self"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:AnimationTests"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid DataContext="{Binding ElementName=self}">
        <StackPanel Orientation="Horizontal">
            <Button Content="Toggle Border" Click="ButtonToggle_Click" />

            <Border x:Name="TestBorder" DataContext="{Binding ElementName=self, Path=TestBorderItem.MyState}"/>
        </StackPanel>
    </Grid>
</UserControl>
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace AnimationTests
{

    public class TestItem : INotifyPropertyChanged
    {
        private int _myState;
        public int MyState
        {
            get => _myState;
            set { _myState = value; OnPropertyChanged(); }
        }
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public partial class AnimationTestControl : UserControl
    {
        public AnimationTestControl()
        {
            InitializeComponent();
        }

        public TestItem TestBorderItem { get; } = new TestItem();

        private void ButtonToggle_Click(object sender, RoutedEventArgs e)
        {
            Task.Run(() =>
            {
                var cnt = 0;
                var max = 100;
                void handler(object s, DependencyPropertyChangedEventArgs args) => cnt++;
                
                TestBorder.DataContextChanged += handler;

                for (int i = 0; i < max; i++)
                {
                    TestBorderItem.MyState = i + 1;
                    Thread.Sleep(2);
                }

                Console.WriteLine($"{max}: {cnt}");

                TestBorder.DataContextChanged -= handler;
            });
        }
    }
}
使用系统;
使用System.Collections.ObjectModel;
使用系统组件模型;
使用System.Runtime.CompilerServices;
使用系统线程;
使用System.Threading.Tasks;
使用System.Windows;
使用System.Windows.Controls;
名称空间动画测试
{
公共类证明项:INotifyPropertyChanged
{
私人国际组织;
我州公共交通
{
get=>\u myState;
设置{u myState=value;OnPropertyChanged();}
}
公共事件属性更改事件处理程序属性更改;
受保护的虚拟void OnPropertyChanged([CallerMemberName]字符串propertyName=null)
{
PropertyChanged?.Invoke(这是新的PropertyChangedEventArgs(propertyName));
}
}
公共部分类AnimationTestControl:UserControl
{
公共AnimationTestControl()
{
初始化组件();
}
公共TestItem TestBorderItem{get;}=new TestItem();
私有无效按钮切换\单击(对象发送方,路由目标)
{
Task.Run(()=>
{
var-cnt=0;
var max=100;
void处理程序(对象s,dependencPropertyChangedEventArgs args)=>cnt++;
TestBorder.DataContextChanged+=处理程序;
对于(int i=0;i
所有带有用户界面元素的操作都通过主应用程序线程的调度程序队列执行。
下面我写的不是一个确切的,而是一个大致的工作场景

当您更改ViewModel中的任何属性时,将创建一个任务来更改UI元素的属性(通过绑定),并将此任务推送到调度程序队列。
下次更改ViewModel属性时,下一个任务将排队。
如果很快更改ViewModel属性的值,则下一个任务很可能会在上一个任务之前排队。
当第一个任务启动时,它将读取ViewModel属性的当前值,而这将不再是第一个属性值,而是第二个属性值。
绑定将更改UI元素的属性值,此更改将触发相应的事件,该事件也将在同一任务中执行。
处理事件后,将完成第一个任务。
一段时间后,第二个任务将开始。
绑定将重新指定ViewModel属性的当前值。 但是元素的UI属性已经包含此值。
由于UI元素的属性值没有更改,因此,相应地,不会生成关于其更改的事件

响应请求添加:

<UserControl x:Class="AnimationTests.AnimationTestControl"
             x:Name="self"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:AnimationTests"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid DataContext="{Binding ElementName=self}">
        <StackPanel Orientation="Horizontal">
            <Button Content="Toggle Border" Click="ButtonToggle_Click" />

            <Border x:Name="TestBorder" DataContext="{Binding ElementName=self, Path=TestBorderItem.MyState}"/>
        </StackPanel>
    </Grid>
</UserControl>
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace AnimationTests
{

    public class TestItem : INotifyPropertyChanged
    {
        private int _myState;
        public int MyState
        {
            get => _myState;
            set { _myState = value; OnPropertyChanged(); }
        }
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public partial class AnimationTestControl : UserControl
    {
        public AnimationTestControl()
        {
            InitializeComponent();
        }

        public TestItem TestBorderItem { get; } = new TestItem();

        private void ButtonToggle_Click(object sender, RoutedEventArgs e)
        {
            Task.Run(() =>
            {
                var cnt = 0;
                var max = 100;
                void handler(object s, DependencyPropertyChangedEventArgs args) => cnt++;
                
                TestBorder.DataContextChanged += handler;

                for (int i = 0; i < max; i++)
                {
                    TestBorderItem.MyState = i + 1;
                    Thread.Sleep(2);
                }

                Console.WriteLine($"{max}: {cnt}");

                TestBorder.DataContextChanged -= handler;
            });
        }
    }
}
如果您能提供一个仅限XAML的解决方案(这将有助于保持逻辑和设计的分离),那就太好了

实施。

要在XAML中使用,您需要修改DataTrigger以检查PropertyChanged事件流上的值。
但是更改数据触发器很棘手。
它的许多成员被声明为
内部

因此,您必须创建自己的中间触发器(CockedTrigger),该触发器将根据给定的条件进行触发。
DataTrigger将绑定到CockedTrigger属性。
完成操作后,必须释放触发器。
为此,必须创建动画中使用的附着特性

CockedTrigger将在资源中创建,需要绑定,因此必须从Freezable派生。
此外,它将部分不在Dispatcher线程中工作,因此需要实现
INotifyPropertyChanged
,并且需要所有必要的依赖