I wrote a WPF application that uses D3dImage class, I subscribed to CompositionTarget.Rendering event and I update the content displayed with the following instructions
D3DImageInstance.Lock();
D3DImageInstance.SetBackBuffer(...);
D3DImageInstance.AddDirtyRect(new Int32Rect(0, 0, Width, Height));
D3DImageInstance.Unlock();
My problem is that if the Window is resized, during the Lock() call the resize event is fired and the program execution jumps to the handler of the event, as shown in the following stack trace:
D3DWPFImageSource.Initialize(D3DImageExtManager d3dImageManager, int width, int height, SharpDX.Direct3D11.Texture2D backBufferTexture, SharpDX.Direct3D11.Texture2DDescription textureDesc, int renderSurfaceCount) Line 61 C#
D3DRenderContextWPF.InitResourceBuffers(System.Drawing.Size size) Line 4407 C#
D3DRenderContextWPF.Resize(System.Drawing.Size size) Line 4589 C#
Workspace.OnResize(System.EventArgs e) Line 3875 C#
WorkspaceBase.CreateAndBindTargets() Line 1670 C#
WorkspaceBase.OnRenderSizeChanged(System.Windows.SizeChangedInfo sizeInfo) Line 1845 C#
[External Code]
> D3DImageInstance.Lock();
D3DImageExtManager.UpdateBackBufferCommand.Update(SharpDX.Direct3D11.Device device, SharpDX.Direct3D11.DeviceContext context, SharpDX.Direct3D11.Texture2D wpfSharedSurface, System.IntPtr sharedSurfacePtr) Line 222 C#
D3DImageExtManager.CompositionTargetOnRendering(object sender, System.EventArgs eventArgs) Line 156 C#
D3DRenderContextWPF.OnRendering() Line 4491 C#
WorkspaceBase.OnRendering(object sender, System.EventArgs e) Line 1785 C#
[External Code]
In the resize handler I re-initialize the graphics resource, which leads to an inconsistent state when the program execution goes back on the Rendering handler.
Is this normal behavior? It depends only on the Lock() call? What is the right approach to manage this case?
Thanks
Here’s how I usually render things with D3DImage:
public void render()
{
bool resized = false;
d3dImage.Lock();
try
{
// That method returns current back buffer as IDirect3DSurface9, without AddRef
IntPtr surface = scene.getSurface();
if( surface == IntPtr.Zero )
return; // IsFrontBufferAvailable is false, the back buffer was destroyed
if( surface != prevSurface )
{
d3dImage.SetBackBuffer( D3DResourceType.IDirect3DSurface9, surface, false );
prevSurface = surface;
resized = true;
}
// That method actually renders the 3D scene into the back buffer
scene.render();
d3dImage.AddDirtyRect( scene.completeRect );
}
finally
{
d3dImage.Unlock();
}
if( !resized )
return;
Application.Current.Dispatcher.BeginInvoke( render, DispatcherPriority.Background );
}
In addition to the above, you also need to handle the events:
OnRenderSizeChanged to re-create the back buffer, and if you use depth, also depth+stencil buffer.
IsFrontBufferAvailableChanged to destroy or again re-create the back buffer.
Solved by calling Dispatcher.DisableProcessing() to prevent events processing during the rendering routine
using System.Windows.Threading.Dispatcher.CurrentDispatcher.DisableProcessing())
{
D3DImageInstance.Lock();
D3DImageInstance.SetBackBuffer(...);
D3DImageInstance.AddDirtyRect(new Int32Rect(0, 0, Width, Height));
D3DImageInstance.Unlock();
}
Related
I have a WPF app with two canvases which overlay each other . . .
<Canvas Name="GeometryCnv" Canvas.Top="0" Canvas.Left="0" Margin="10,21,315,251" />
<Canvas Name="ROIcnv" Background ="Transparent" Canvas.Top="0" Canvas.Left="0" Margin="10,21,315,251" MouseDown="ROIcnvMouseDown" MouseUp="ROIcnvMouseUp" MouseMove="ROIcnvMouseMove"/>
I draw some geometry on the first canvas and I draw a rectangle to denote a Region on Interest (ROI) on the second one, using the Mouse-down event to start the drawing, Mouse-move events while drawing (resizing or positioning) the rectangle, and the Mouse-up event to end the drawing.
Except that it's not handling the events reliably. It gets the initial Mouse-down event to start it. It gets Mouse-move events continuously - regardless of whether the mouse is moving - and it does not get the Mouse-up event at all, nor does it get any subsequent mouse down events, say if I double-click the mouse.
The event-handler code looks like this . . .
private void ROIcnvMouseDown(object sender, MouseButtonEventArgs e)
{
MouseLineBegin = Mouse.GetPosition(ROIcnv);
bMouseDown = true;
}
private void ROIcnvMouseUp(object sender, MouseButtonEventArgs e)
{
MouseLineEnd = Mouse.GetPosition(ROIcnv);
bMouseDown = false;
}
private void ROIcnvMouseMove(object sender, MouseEventArgs e)
{
iMM++; // counting mouse move events
ROIcnv.Children.Clear(); // clear the ROI canvas
if (bMouseDown) // if we're drawing now
{
MouseLineEnd = Mouse.GetPosition(ROIcnv);
// get the upper left and lower right = coords from the beginning and end points . . .
int ulx = 0;
int uly = 0;
int lrx = 0;
int lry = 0;
if (MouseLineEnd.X >= MouseLineBegin.X)
{
ulx = (int) MouseLineBegin.X;
lrx = (int) MouseLineEnd.X;
}
else
{
lrx = (int)MouseLineBegin.X;
ulx = (int)MouseLineEnd.X;
}
if (MouseLineEnd.Y >= MouseLineBegin.Y)
{
uly = (int)MouseLineBegin.Y;
lry = (int)MouseLineEnd.Y;
}
else
{
lry = (int)MouseLineBegin.Y;
uly = (int)MouseLineEnd.Y;
}
int h = Math.Abs(lry-uly);
int w = Math.Abs(lrx-ulx);
var rect = new Path
{
Data = new RectangleGeometry(new Rect(ulx, uly, w, h)),
Stroke = Brushes.Black,
StrokeThickness = 2
};
ROIcnv.Children.Add(rect);
}
}
... I've tried suspending the mouse in mid-air and resting it on towels to eliminate any vibrations that might cause spurious move events with no benefit, any anyway that wouldn't account for not getting subsequent up and down events.
Note: I tried this on another computer with exactly the same results.
You'll have much better responses if you provide a minimal, working example of your problem (specifically both your ROIcnvMouseDown and ROIcnvMouseUp methods are missing as are all of your property declarations). The problem is possibly due to your newly-created Path object interfering with the mouse messages, if so then it can be fixed by setting it's IsHitTestVisible property to false. Need a minimal example to determine this for sure though.
UPDATE: Sorry, my bad, I must have stuffed up the cut-n-paste into my test app. Try capturing the mouse in response to the mouse down event:
private void ROIcnvMouseDown(object sender, MouseButtonEventArgs e)
{
MouseLineBegin = Mouse.GetPosition(ROIcnv);
bMouseDown = true;
Mouse.Capture(sender as IInputElement);
}
And of course you need to release it in response to MouseUp:
private void ROIcnvMouseUp(object sender, MouseButtonEventArgs e)
{
MouseLineEnd = Mouse.GetPosition(ROIcnv);
bMouseDown = false;
Mouse.Capture(sender as IInputElement, CaptureMode.None);
ROIcnv.Children.Clear();
}
The other thing I've done is call ROIcnv.Children.Clear(); in response to MouseUp as I assume you no longer want the selection rectangle to be visible. On my machine this doesn't result in any spurious mouse move events.
Does that answer the question?
I'm trying to make pretty effect with not using Storyboard or another ready/already done stuff in WPF.
I want to make smooth effect, where on some event (like click) the UI element resizes for 2-3 seconds and bluring with changing color. All these items I want to make in smooth pretty way.
I have prepared such class to render each frame of my effect:
public static class ApplicationHelper
{
[SecurityPermissionAttribute(SecurityAction.Demand,
Flags=SecurityPermissionFlag.UnmanagedCode)]
public static void DoEvents(DispatcherPriority priority)
{
DispatcherFrame frame = new DispatcherFrame();
DispatcherOperation oper = Dispatcher.CurrentDispatcher.
BeginInvoke(priority,
new DispatcherOperationCallback(ExitFrameOperation),
frame);
Dispatcher.PushFrame(frame);
if (oper.Status != DispatcherOperationStatus.Completed)
{
oper.Abort();
}
}
private static object ExitFrameOperation(object obj)
{
((DispatcherFrame)obj).Continue = false;
return null;
}
[SecurityPermissionAttribute(SecurityAction.Demand,
Flags=SecurityPermissionFlag.UnmanagedCode)]
public static void DoEvents()
{
DoEvents(DispatcherPriority.Background);
}
}
Here I'm trying to make it work with DispatcherTimer:
void vb1_click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
DispatcherTimer dt = new DispatcherTimer();
dt.Interval = new TimeSpan(0, 0, 0, 0, 500);
dt.Tick += new System.EventHandler(dt_Tick);
dt.Start();
}
void dt_Tick(object sender, System.EventArgs e)
{
for(int i = 0; i < 20; i++)
{
this.vb2_blur_eff.Radius = (double)i;
ApplicationHelper.DoEvents();
}
}
The main problem is, that when I'm launcing it, I'm only waiting and at the final time ( when must last frame be rendered ) , I'm getting in a very quick speed all frames, but perviously there was nothing.
How to solve it and make perfect smooth effect in pure C# way with not using some ready/done stuff?
Thank you!
The ApplicationHelper.DoEvents() in dt_Tick probably does nothing, since there are no events to process. At least not the ones you're probably expecting.
If I'm not mistaken, your code will just quickly set the Radius to 0, then 1, 2, and so on in quick succession, and finally to 19. All of that will happen every 500 milliseconds (on every Tick, that is).
I think you might believe that each Tick will only set Radius to one value and then wait for the next Tick, but it does not. Every Tick will set the Radius to all the values, ending at 19. That is one possible explanation for what you're experiencing.
I would also like to comment on the DoEvents approach. It's most likely a bad idea. Whenever I see a DoEvents I get chills up my spine. (It reminds me of some seriously bad Visual Basic 5/6 code I stumbled across 10-15 years ago.) As I see it, an event handler should return control of the GUI thread as quickly as possible. If the operation takes a not insignificant amount of time, then you should delegate that work to a worker thread. And nowadays, you have plenty of options for writing asynchronous code.
I got a little problem with button events. I programmed one button to decrease specific value by 1 (click), and I want to decrease it over time while holding button pressed. I'm using Silverlight, not XNA.
myTimer.Change(0, 100);
private void OnMyTimerDone(object state)
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
if (rightButton.IsPressed)
{
rightButton_Click(null, null);
}
});
}
this code is correctly working at the beginning, but then I am unable to single tap as it is always calling hold event.
Try to use the RepeatButton silverlight control instead of using a normal Button
Here is an Example of how to use it:
XAML Code:
<RepeatButton x:Name="rbtnDecrease" Content="Decrease" Delay="200" Interval="100" Click="rbtnDecrease_Click" />
Delay: The amount of time, in milliseconds, the RepeatButton waits while it is pressed before it starts repeating.
Interval: The amount of time, in milliseconds, between repeats once repeating starts.
C# Code:
private int tempCount = 100; // A temp Variable used as an Example
private void rbtnDecrease_Click(object sender, RoutedEventArgs e){
// Add your Button Click/Repeat Code Here...
// Example of Decreasing the value of a Variable
tempCount--;
}
Two suggestions, the first being to stop the timer (using a DispatcherTimer) if isPressed is false
void Button_MouseLeftButtonDown(object sender, EventArgs e)
{
myTimer.Start();
}
void OnTimerTick(object s, EventArgs args)
{
if(rightButton.IsPressed == false)
{
myTimer.Stop();
}
else
{
// decrease value
}
}
the second being to stop the timer on the MouseLeftButtonUp event
I have a grid in my application. After user selects some files in ofdialog application processes some calculations. While app is making calculations it looks like it is not responding. How to display some picture and make main window in black&white while calculating? Maybe make some dp in MainWindow a la "IsBusy" and bind a popup with picture to it?
How you implement this logic in yours apps?
One easy way is to use the busy indicator from Extended WPF Toolkit:
Dowload the binaries and add project reference to WPFToolkit.Extended.dll.
Next add following namespace in your 'main window':
xmlns:ext="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit.Extended"
Then add the busy indicator in the view (place it so that when shown, it will occupy the whole screen) Here my main window has two rows and I want the control to span on both rows. The control's IsBusy property is bound to a bool property in the view's data context.
<ext:BusyIndicator Grid.RowSpan="2" x:Name="busyIndicator" IsBusy="{Binding IsBusy}" />
The long lasting calculation should be processed in another thread so that it won't block the user interface. For threading you can use BackgroundWorker class.
You should have the long running tasks in a seperate thread to avoid UI blocking.
Here's one way you could achieve that:
Define background thread as below:
//Delegate that you could pass into the worker thread
public delegate void ProgressMonitor(string s);
//Call this to start background work
void StartLongRunningWork(ProgressMonitor mon)
{
using (BackgroundWorker bgw = new BackgroundWorker())
{
bgw.DoWork += WorkerThread;
bgw.RunWorkerCompleted += WorkerThreadCompleted;
bgw.RunWorkerAsync(mon);
}
}
void WorkerThread(object sender, DoWorkEventArgs e)
{
ProgressMonitor pm = (ProgressMonitor)e.Argument;
WorkerActual(pm, <any other parameters>);
}
void WorkerActual(ProgressMonitor pm,<any other parameters>)
{
...
pm("Doing x");
Do long running task
pm("Doing y");
...
}
//This function is called in case of Exception, Cancellation or successful completion
//of the background worker. Handle each event appropriately
void WorkerThreadCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
//Long running task threw an exception
}
else
if (e.Cancelled)
{
//Long running task was cancelled
}
else
{
//Long running task was successfuly completed
}
}
And Call it as below:
private void UpDateProgressLabel(string s)
{
this.Dispatcher.BeginInvoke((Action)delegate
{
NotificationLabel.Content = s;
});
}
private void Button_Click(object sender, RoutedEventArgs e)
{
StartLongRunningWork(UpDateProgressLabel);
}
I have a Window set to the height and width of my monitors:
var r = System.Drawing.Rectangle.Union( System.Windows.Forms.Screen.AllScreens[0].Bounds, System.Windows.Forms.Screen.AllScreens[1].Bounds );
Height = r.Height;
Width = r.Width;
This is all fine until I Lock my computer (WIN+L), when I come back the window has resized itself to be on one monitor only.
What I want to do is prevent the decrease in size, as I'm drawing on a canvas on the second monitor, and when the resize occurs, this is all lost..
Any thoughts on how I can prevent this?
Cheers!
You can use the Unlock/Lock event in .NET. Store your window height, width and position during the lock event and restore it on an Unlock event. Make sure you add "using Microsoft.Win32"
SystemEvents.SessionSwitch += new SessionSwitchEventHandler(SystemEvents_SessionSwitch);
private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
if (e.Reason == SessionSwitchReason.SessionUnlock)
{
//Put resize logic here
}
else if (e.Reason == SessionSwitchReason.SessionLock)
{
//Put size store logic here
}
}