I have an OpenTK GLControl embedden in a WindowsFormsHost in my WPF application.
I want to continuously update and render it.
In Winforms a solution would be to attach the UpdateAndRender method to the Application.Idle event, but there is no such thing in WPF.
So what would be the best way to do (60FPS) updating of my scene and GLControl ?
You can use a System.Timers.Timer to control how often your render code is called. In your window containing the GLControl-in-WindowsFormsHost, declare a private System.Timers.Timer _timer;, then when you're ready to start the rendering loop, set the timer interval and it's event handler, then start it up, as in the following example:
private void btnLoadModel_Click(object sender, RoutedEventArgs e)
{
LoadModel(); // do whatever you need to do to prepare your scene for rendering
_timer = new System.Timers.Timer(10.0); // in milliseconds - you might have to play with this value to throttle your framerate depending on how involved your update and render code is
_timer.Elapsed += TimerElapsed;
_timer.Start();
}
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
UpdateModel(); // this is where you'd do whatever you need to do to update your model per frame
// Invalidate will cause the Paint event on your GLControl to fire
_glControl.Invalidate(); // _glControl is obviously a private reference to the GLControl
}
You'll clearly need to add using System.Timers to your usings.
You can use Invalidate() for it. This causes the GLControl to redraw it's content.
If you call it at the end of Paint() you may blocking some UI rendering of the other WPF controls.
WPF provides a per frame render event: CompositionTarget.Rendering. This event is called before WPF wants to render the content. Subscribe from it and call Invalidate:
public YourConstructor()
{
//...
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
_yourControl.Invalidate();
}
You need to unsubscribe if you don't use it anymore (to avoid memory leaks).
Here is a How to: Render on a Per Frame Interval Using CompositionTarget from MSDN.
I use GLControl with that method and it works fine. I did not checked how much FPS I have but it feels smooth.
You may also have a look on this: Why is Frame Rate in WPF Irregular and Not Limited To Monitor Refresh?
Related
I've written a Windows Forms app where I do custom drawing on a Panel using Control.CreateGraphics(). Here's what my Form looks like at startup:
The custom drawing is performed on the top panel in the Click event handler of the "Draw!" button. Here's my button click handler:
private void drawButton_Click(object sender, EventArgs e)
{
using (Graphics g = drawPanel.CreateGraphics())
{
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.Clear(Color.White);
Size size = drawPanel.ClientSize;
Rectangle bounds = drawPanel.ClientRectangle;
bounds.Inflate(-10, -10);
g.FillEllipse(Brushes.LightGreen, bounds);
g.DrawEllipse(Pens.Black, bounds);
}
}
After a click on drawButton, the form looks like this:
Success!
But when I shrink the form by dragging a corner...
...and expand it back to its original size,
part of what I drew is gone!
This also happens when I drag part of the window offscreen...
...and drag it back onscreen:
If I minimize the window and restore it, the whole image is erased:
What is causing this? How can I make it so the graphics I draw are persistent?
Note: I've created this self-answered question so I have a canonical Q/A to direct users to, as this is a common scenario that's hard to search for if you don't already know the cause of the problem.
TL;DR:
Don't do your drawing in response to a one-time UI event with Control.CreateGraphics. Instead, register a Paint event handler for the control on which you want to paint, and do your drawing with the Graphics object passed via the PaintEventArgs.
If you want to paint only after a button click (for example), in your Click handler, set a boolean flag indicating that the button has been clicked and then call Control.Invalidate(). Then do your rendering conditionally in the Paint handler.
Finally, if your control's contents should change with the size of the control, register a Resize event handler and call Invalidate() there too.
Example code:
private bool _doCustomDrawing = false;
private void drawPanel_Paint(object sender, PaintEventArgs e)
{
if (_doCustomDrawing)
{
Graphics g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.Clear(Color.White);
Size size = drawPanel.ClientSize;
Rectangle bounds = drawPanel.ClientRectangle;
bounds.Inflate(-10, -10);
g.FillEllipse(Brushes.LightGreen, bounds);
g.DrawEllipse(Pens.Black, bounds);
}
}
private void drawButton_Click(object sender, EventArgs e)
{
_doCustomDrawing = true;
drawPanel.Invalidate();
}
private void drawPanel_Resize(object sender, EventArgs e)
{
drawPanel.Invalidate();
}
But why? What was I doing wrong, and how does this fix it?
Take a look at the documentation for Control.CreateGraphics:
The Graphics object that you retrieve through the CreateGraphics method should not normally be retained after the current Windows message has been processed, because anything painted with that object will be erased with the next WM_PAINT message.
Windows doesn't take responsibility for retaining the graphics you draw to your Control. Rather, it identifies situations in which your control will require a repaint and informs it with a WM_PAINT message. Then it's up to your control to repaint itself. This happens in the OnPaint method, which you can override if you subclass Control or one of its subclasses. If you're not subclassing, you can still do custom drawing by handling the public Paint event, which a control will fire near the end of its OnPaint method. This is where you want to hook in, to make sure your graphics get redrawn every time the Control is told to repaint. Otherwise, part or all of your control will be painted over to the control's default appearance.
Repainting happens when all or part of a control is invalidated. You can invalidate the entire control, requesting a full repaint, by calling Control.Invalidate(). Other situations may require only a partial repaint. If Windows determines that only part of a Control needs to be repainted, the PaintEventArgs you receive will have a non-empty ClipRegion. In this situation, your drawing will only affect the area in the ClipRegion, even if you try to draw to areas outside that region. This is why the call to drawPanel.Invalidate() was required in the above example. Because the appearance of drawPanel needs to change with the size of the control and only the new parts of the control are invalidated when the window is expanded, it's necessary to request a full repaint with each resize.
I am trying to create a drawing with 5000 Shape objects in a background thread on a canvas.
I use the asyn-await pattern:
async void CreateDrawingAsync()
{
await Task.Run(() => CreateDrawing()).ConfigureAwait(false);
}
void CreateDrawing()
{
DrawObjects = new ObservableCollectionEx<DrawObject>();
// or: DrawObjects.Clear();
RaisePropertyChanged("DrawObjects");
// etc ... etc ...
}
ObservableCollectionEx means I use an extension of ObservableCollection to add an object to the collection via the Dispatcher.
When I start CreateDrawingAsync in the Loaded event of the Window (in the ctor of Data) the UI is unresponsive.
Using DispatcherPriority.Background the items are added one by one in the UI, but also in that case, the UI is unresponsive.
Loaded += (object sender, RoutedEventArgs e) =>
{
DataContext = new Data(2000, 1000, 1000);
};
1) I expected the background thread would resolve the unresponsive UI issue, what am I overlooking?
2) Why does RaisePropertyChanged("DrawObjects") (see code above) have no effect? I would have expected the drawing would be cleared due to the propertychanged.
You should not do UI operations on a background thread. This includes:
Drawing on a canvas.
Raising PropertyChanged notifications.
Creating or updating an ObservableCollection.
Creating a fake-UI component like ObservableCollectionEx that just forwards all its work to the UI thread doesn't gain you anything.
What's the best way to detect idle state for a silverlight application? I have read quite a few articles on the net by now and usually they are either for wpf/mobile apps etc.
I have created a DispatcherTimer which locks the screen after 5 minutes and it seems that I will have to go to every widget in every screen(my application has around 4-5 screens) and add a mousebuttondown or mouseenter eventhandler to reset this timer. This doesn't seem to be efficient but just adding the handler to the layroot is not helping either.
Any helpful suggestions?
Thanks
You don't need to modify every control. If you add the following code on startup:
Application.Current.RootVisual.MouseMove += new MouseEventHandler(RootVisual_MouseMove);
Application.Current.RootVisual.KeyDown += new KeyEventHandler(RootVisual_KeyDown);
With the following event handlers:
private void RootVisual_KeyDown(object sender, KeyEventArgs e)
{
idle = false;
}
private void RootVisual_MouseMove(object sender, MouseEventArgs e)
{
idle = false;
}
Where idle is the variable you use in your DispatcherTimer Tick event to check if things are happening or not.
As events bubble up the tree this should work for all your controls.
Handled events will not bubble up to root control. Instead you should use the AddHandler method with handledEventsToo = true.
I am trying to create a custom scrollbar and am using images as button.
For now a simple
I can handle the MouseLeftButtonDown and Up event just fine but what I'd like to do is while its held down, every so many millisecond trigger an event is fired.
I tried something like this but it isn't quite working. Suggestions?
public delegate void Changed(RangeScrollButtonControl sender, int value);
public event Changed OnChanged;
private System.Threading.Timer Timer;
private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.Timer = new System.Threading.Timer(Timer_Callback, null, 0, 100);
}
private void Image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
this.Timer = null;
}
private void Timer_Callback(object sender)
{
if (this.OnChanged != null)
{
this.OnChanged(this, 1);
}
}
The piece of functionality you're looking for is a RepeatButton, this will fire it's Click event repeatedly while the mouse button is held down. You can configure the delay and the interval of the events.
You could then style this button to use the image at Silverlight Show
Hope this helps.
Which piece "isn't quite working" ?
Also, could you restyle or retemplate Silverlght's scrollbar similar to what is seen in this blog post to get what you need?
I would use a Storyboard as a timer. Something like:
Then you can do a MouseSTB.Begin. Once the Storyboard is finished you can catch it in the MouseSTB.Completeed Event. In that event you can do what ever you need to do and then just start it over again. It can easilly be controled by setting some flags on the mouseover, mouseenter and mouseleave events. I use these timers in a lot of place and they work just fine and they do not peg the processor.
In a WPF app, is there a object I can assign to FileSystemWatcher.SynchronizingObject?
I can make my own, but if there is one available, I would like to use it.
Reflector shows that the only class that implements ISynchronizeInvoke (i.e., the type of the FileSystemWatcher.SynchronizingObject property) is System.Windows.Form.Control (and its subclasses); there do not appear to be any WPF objects that implement this interface.
You need to create an ISynchronizeInvoke object that wraps the System.Windows.Threading.Dispatcher instance from the Window. That class is the closest thing WPF has to an ISynchronizeInvoke object.
Inside the wrapper class, simply forward the BeginInvoke call to the dispatcher you've wrapped. I did a bit extra work and also wrapped the DispatcherOperation that results from the Dispatcher.BeginInvoke method to call its Wait method inside the ISynchronizeInvoke.EndInvoke method.
Everything seems to be working correctly so far, it's too bad Microsoft didn't see fit to have the Dispatcher class implement the interface for ease of use.
There is one way. FileSystemWatcher when you enabling events (EnableRaisingEvents = true) creates it's own thread to monitor the FS events. Via ISynchronizeInvoke it can async invoke members of your Form for example, (it's thread can async interact with the main thread - UI thread).
In WPF there's no implementation of ISynchronizeInvoke, but there is a possibility to
interact with the UI thread, via Dispatched property of your Window like this:
var fsw = new FileSystemWatcher()
{
//Setting the properties: Path, Filter, NotifyFilter, etc.
};
fsw.Created += (sender, e) =>
{
Dispatcher.Invoke(new Action<params_types>((params_identifiers) =>
{
//here the code wich interacts with your IU elements
}), here_params);
};
//... in this way (via Dispatcher.Invoke) with the rest of events
fsw.EnableRaisingEvents = true;
Use the DispatcherTimer rather than the system timer.
This will work fine for WPF.
DispatcherTimer t1 = new DispatcherTimer();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
t1.Interval = new TimeSpan(0,0,0,0,200);
t1.Tick += new EventHandler(t1_Tick);
t1.Start();
...
void t1_Tick(object sender, EventArgs e)
{
//some work
}