简体   繁体   中英

Changing Opacity With Reactive Extensions

<rant>Yes, I know this would be easier to implement in WPF. I hear that a lot. Sadly, It is not possible. </rant>

I am writing a WinForms app, and I need to "fade" a Control in & out. 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. This seems like a perfect time to work on my 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. 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.

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.

... or maybe this is the wrong approach altogether.

I'm open to other suggestions.

I can't help you with the WinForms transparency part. 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. Subscribe returns a IDisposable . You should dispose of it in your Closed event.

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 .

Finally, Interval actually returns a count. We can use that to simplify your observable by just calculating the desired 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();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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