简体   繁体   English

使用反应式扩展程序更改不透明度

[英]Changing Opacity With Reactive Extensions

<rant>Yes, I know this would be easier to implement in WPF. <rant>是的,我知道在WPF中更容易实现。 I hear that a lot. 我听到很多。 Sadly, It is not possible. 可悲的是,这是不可能的。 </rant> </ rant>

I am writing a WinForms app, and I need to "fade" a Control in & out. 我正在编写WinForms应用程序,并且需要“渐入渐出”控件。 Transparency is all but impossible in WinForms, so I am trying to use opacity: the idea is to change the Alpha channel of each child control's ForeColor property over a period of time. 在WinForms中,透明度几乎是不可能的,因此我尝试使用不透明度:想法是在一段时间内更改每个子控件的ForeColor属性的Alpha通道。 This seems like a perfect time to work on my Reactive Extensions! 这似乎是我的Reactive Extensions工作的绝佳时机!

My possible solution is: 我可能的解决方案是:

private void FadeOut()
{
   // our list of values for the alpha channel (255 to 0)
   var range = Enumerable.Range(0,256).Reverse().ToList();

   // how long between each setting of the alpha (huge value for example only)
   var delay = Timespan.FromSeconds(0.5);

   // our "trigger" sequence, every half second
   Observable.Interval(delay)
        // paired with the values from the range - we just keep the range
          .Zip(range, (lhs, rhs) => rhs)
        // make OnNext changes on the UI thread
          .ObserveOn(SynchronizationContext.Current)
        // do this every time a value is rec'd from the sequence
          .Subscribe(
              // set the alpha value
              onNext:ChangeAlphaValues, 
              // when we're done, really hide the control
              onCompleted: () => Visible = false, 
              // good citizenry
              onError: FailGracefully);
}

// no need to iterate the controls more than once - store them here
private IEnumerable<Control> _controls;

private void ChangeAlphaValues(int alpha)
{
    // get all the controls (via an extension method)
    var controls = _controls ?? this.GetAllChildControls(typeof (Control));

    // iterate all controls and change the alpha
    foreach (var control in controls)
       control.ForeColor = Color.FromArgb(alpha, control.ForeColor);
}

... which looks impressive, but it doesn't work. ...看起来不错,但不起作用。 The really impressive part is that it doesn't work in two ways ! 真正令人印象深刻的部分是它不能以两种方式起作用! Gonna get a "Far Exceed" on my next performance review if I keep this up. 如果我坚持下去,我的下一次性能评测将获得“超额成绩”。 :-) :-)

  1. (Not really the Rx part of the problem) The alpha values don't actually seem to make any difference. (不是问题的Rx部分)Alpha值实际上似乎没有任何区别。 Despite setting the values, the display looks the same. 尽管设置了值,但显示看起来还是一样。
  2. If I close the window before the sequence is complete, I get the following error: 如果在序列完成之前关闭窗口,则会出现以下错误:

System.InvalidOperationException was unhandled Message: An unhandled exception of type 'System.InvalidOperationException' occurred in System.Reactive.Core.dll Additional information: Invoke or BeginInvoke cannot be called on a control until the window handle has been created. 尚未处理System.InvalidOperationException消息:System.Reactive.Core.dll中发生了'System.InvalidOperationException'类型的未处理异常。其他信息:在创建窗口句柄之前,无法在控件上调用Invoke或BeginInvoke。

I assume this is where a cancellation token would come in handy - but I have no idea how to implement it. 我认为这是取消令牌会派上用场的地方-但我不知道如何实现它。

I need guidance on: 我需要以下方面的指导:

How to close the window gracefully (ie without throwing an error) if the sequence is still running, and how to get the alpha values for the colors to actually display after they're changed. 如果序列仍在运行,如何优雅地关闭窗口(即不引发错误),以及如何获取更改后的颜色实际显示的alpha值。

... or maybe this is the wrong approach altogether. ...或者这完全是错误的方法。

I'm open to other suggestions. 我愿意接受其他建议。

I can't help you with the WinForms transparency part. 对于WinForms透明性部分,我无能为力。 Maybe you should split the question in two. 也许您应该将问题分成两部分。

But for the Rx part, you just need to cancel your subscription when the window is closed. 但是对于Rx部分,您只需要在关闭窗口时取消订阅即可。 Subscribe returns a IDisposable . Subscribe返回IDisposable You should dispose of it in your Closed event. 您应该在Closed事件中处理它。

And, since I assume this fade animation might be invoked multiple times before the window is closed, we can make use of an Rx helper SerialDisposable . 并且,由于我假设此淡入淡出动画可能在关闭窗口之前被多次调用,因此我们可以使用Rx帮助器SerialDisposable

Finally, Interval actually returns a count. 最后, Interval实际上返回一个计数。 We can use that to simplify your observable by just calculating the desired alpha. 我们可以通过仅计算所需的alpha值来简化可观察性。

private SerialDisposable _fadeAnimation = new SerialDisposable();

private void FadeOut()
{
    // how long between each setting of the alpha (huge value for example only)
    var delay = Timespan.FromSeconds(0.5);

    _fadeAnimation.Disposable = Observable
        .Interval(delay)
        // 256 animation steps
        .Take(256)
        // calculate alpha
        .Select(i => 255 - i)
        .ObserveOn(SynchronizationContext.Current)
        .Subscribe(
            // set the alpha value
            onNext:ChangeAlphaValues, 
            // when we're done, really hide the control
            onCompleted: () => Visible = false, 
            // good citizenry
            onError: FailGracefully);
}

private void Form1_Closed()
{
    _fadeAnimation.Dispose();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM