我有两个WPF(来自标准集)小部件A和B.当我更改A的某个属性时,它应该在B上设置,当它在B中更改时,它应该在A上设置。
现在我有这个丑陋的递归 - >我改变了A,所以代码改变了B,但是由于B被改变了,它改变了A,所以它改变了B ......你有了图片。
如何避免这种递归最“标准”的方式?天真地删除和添加事件处理程序不起作用,并且检查新值是否与旧值相同在这里不适用(因为计算的波动 - 我没有为A和B设置相同的值,但是已转换)
我总是尝试提供有关问题的最低限度信息以避免混淆。但是,这可能会有所帮助
尽管标题为“递归触发”,处理程序会按顺序调用,因此您可以使用序列entry-exit-entry-exit-entry-exit,而不是entry-entry-entry-exit-exit-exit < / p>
和最后一个,可能是最不重要的,但不过
A和B(在这种情况下)是滚动查看器,我尝试按比例保持两个相同的位置。该项目(由Karin Huber提供)在这里: http://www.codeproject.com/KB/WPF/ScrollSynchronization.aspx
阻止事件的想法如此受欢迎,我添加了触发事件的顺序,我们走了:
如你所见,这是徒劳的。
答案 0 :(得分:3)
首先,我会考虑设计,因为这些循环依赖通常是糟糕设计的标志。
但是,可能存在这种依赖性是唯一的方法。在这种情况下,我建议使用私有标志来指示B的变化是否是由A的变化引起的。像这样(更新):
public class A
{
private bool m_ignoreChangesInB = false;
private void B_ChangeOccurred(object sender, EventArgs e)
{
if (!m_ignoreChangesInB)
{
// handle the changes...
}
}
private void SomeMethodThatChangesB()
{
m_ignoreChangesInB = true;
// perform changes in B...
m_ignoreChangesInB = false;
}
}
应该在B类中使用相同的方法。但是,此方法不处理来自多个线程的更改。如果可能同时从多个线程更改A或B,则必须使用适当的技术来避免属性更改丢失。
答案 1 :(得分:2)
不是引发事件,而是重构代码,以便A和B的事件处理程序调用另一种方法来完成实际的工作。
private void EventHandlerA(object sender, EventArgs e)
{
ChangeA();
ChangeB();
}
private void EventHandlerB(object sender, EventArgs e)
{
ChangeB();
ChangeA();
}
如果您需要在直接或通过B更改A时需要进行微妙的不同操作,则可以扩展/更改这些方法。
<强>更新强>
鉴于您无法更改/无法访问代码,这不是解决方案。
答案 2 :(得分:0)
ChrisFs解决方案可能就是这样,但有时我们会删除事件,进行更改并重新添加事件处理程序:
想象一下DataContext,使用DataContextChanged事件:
DataContextChanged -= OnDataContextChanged;
DataContext = new object();
DataContextChanged += OnDataContextChanged;
通过这种方式,您可以确定您的事件处理程序不会消失。然而事件仍然会发生;)。
答案 3 :(得分:0)
并检查新值是否与旧值相同在此处不适用(因为计算的波动 - 我没有将相同的值设置为A和B,但已转换)。
所以你说的是这样的事情发生了:
A.Foo
设置为 x 。A_FooChanged
将B.Bar
设置为f( x )。B_BarChanged
将A.Foo
设置为g(f( x )),这不是 x 。A_FooChanged
将B.Bar
设置为f(g(f( x )))。等等。它是否正确?因为如果g(f( x )) x ,那么解决方案的简单:B_BarChanged
应该只设置A.Foo
} if A.Foo
!= g(f( x )。
假设此递归没有可计算的结束状态,那么事件处理程序需要某种方式来知道触发它们处理的事件的上下文。您无法从常规事件协议中获取该信息,因为事件旨在解耦此设计耦合的操作。
听起来你需要一种带外方式让这些控件相互发出信号。它可以像使用HashSet<EventHandler>
Window
的属性一样简单。我会考虑这样的事情:
private void A_FooChanged(object sender, EventArgs e)
{
if (!SignalSet.Contains(B_BarChanged))
{
SignalSet.Add(A_FooChanged);
B.Bar = f(A.Foo);
SignalSet.Remove(A_FooChanged);
}
}
如果A设置B.Bar
,B设置C.Baz
,C设置A.Foo
,则会出现故障,但我怀疑如果发生这种情况,要求本身就会崩溃。在这种情况下,您可能不得不求助于查看堆栈跟踪。那不是很好,但是这个问题没什么了不起。
答案 4 :(得分:0)
稍微有点狡猾的解决方案是设置一个中断时间段,在此期间忽略后续的事件处理程序激活。如果您这样做,请务必使用DateTime.UtcNow
而不是DateTime.Now
来避免夏令时边界条件。
如果您不希望依赖于计时(事件可能会合法地快速更新),您可以使用类似的阻止类型,尽管这更加苛刻:
private int _handlerCounter;
private void Handler()
{
//The logic of this handler will trigger an 'asynchronously reentrant' callback, so we ignore the next (and only the next) callback
//Note that this breaks down if the callback is not triggered, so we need to make certain the reentrancy will occur
//If we can't ensure that, we need to at least detect that it won't occur and manually decrement the counter
if (Interlocked.CompareExchange(ref _handlerCounter, 0, 1) == 0)
{
//Call set on A/B, which triggers callback of this same handler for B/A
}
else
{
Interlocked.Decrement(ref _handlerCounter);
}
}
答案 5 :(得分:0)
因为在ScrollViewers之间缩放或以其他方式更改位置,您不能使用简单的绑定,但转换器是否有效?
<Window x:Class="Application1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SurfaceApplication21"
Title="SurfaceApplication21"
>
<Window.Resources>
<local:InvertDoubleConverter x:Key="idc" />
</Window.Resources>
<Grid>
<StackPanel>
<Slider Minimum="-100" Maximum="100" Name="a" Height="23" HorizontalAlignment="Left" Margin="30,12,0,0" VerticalAlignment="Top" Width="100" />
<Slider Minimum="-100" Maximum="100" Value="{Binding ElementName=a, Path=Value, Converter={StaticResource idc}}" Name="b" Height="23" HorizontalAlignment="Left" Margin="30,12,0,0" VerticalAlignment="Top" Width="100" />
</StackPanel>
</Grid>
</Window>
代码,您可以在其中实现在两个视图之间缩放所需的任何数学。
[ValueConversion(typeof(double), typeof(double))]
public class InvertDoubleConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo ci)
{
return -(double)value;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo ci)
{
return -(double)value;
}
}
我没有尝试使用ScrollViewers,但由于属于ScrollViewer模板的Scrollbars和Sliders都来自RangeBase,这样的东西应该可以工作,但你可能需要重新模板化和/或子类化你的ScrollViewers。 / p>
答案 6 :(得分:0)
我不熟悉WPF控件,因此以下解决方案可能不适用: