WPF Unfreeze UI when showing heavy window - wpf

I have heavy window (Devexpress UI), and, when I call window.Show() in my ICommand body, UI freezes. I want to show some progress while window loaded.
I was trying to call it in async method, but could not find any way to call Show in async way:
I was try Task.Run but it fail with exception of different UI threads.
I was try to call Application.Dispatcher.BeginInvoke but this also freezes UI.
Little adjustment:
I was wrong about Show() freezes. UI freeze at constructor call: var window = new ProductsPricesWindow()

If your execute some time-consuming work in another thread, then you must synchronize a result of another thread with UI thread. To synchronize two threads(new thread and UI thread), it is necessary to use Dispatcher.
As MSDN says:
Only one thread can modify the UI thread. But how do background
threads interact with the user? A background thread can ask the UI
thread to perform an operation on its behalf. It does this by
registering a work item with the Dispatcher of the UI thread. The
Dispatcher class provides two methods for registering work items:
Invoke and BeginInvoke. Both methods schedule a delegate for
execution. Invoke is a synchronous call – that is, it doesn’t return
until the UI thread actually finishes executing the delegate.
BeginInvoke is asynchronous and returns immediately.
For example:
xaml:
<TextBox Name="textBox"/>
in code-behind:
Task.Run(()=> {
Thread.Sleep(5000);//imitate time consuming work
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Render,
new Action(() => {
textbox.Text="Hello World!:)"}));
});
In the above example, we create a new thread (Task.Run(...)) and synchronize result of new thread with UI thread by calling Dispatcher(Dispatcher.BeginInvoke(...))
UPDATE:
You should call new window on UI thread. So the code would be:
var yourWindow=new YourWindow();
yourWindow.ShowDialog();
However, as you did not show any code located in constructor of YourWindow, I try to suppose that there is some method which is time consuming. Then your code should look like this:
public class YourWindow()
{
public YourWindow()
{
Task.Run(()=> {
TimeConsumingMethod();
});
}
private void TimeConsumingMethod()
{
Thread.Sleep(5000);//imitate time consuming work
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Render,
new Action(() => {
textbox.Text="Hello World!:)"}
}
}

Looks like this is not possible to make UI responsible when other UI component initialized.
So in my case solution is just allow UI to show in BusyIndicator (I use Xceed.Wpf.Toolkit) current state.
To do this I just put await Task.Delay(100); before creating window.
Also was helpful to pre-initialize creating window in background thread that starts on my app start. This makes creatng new window more responsible and even makes BusyIndicator live sometime.
public void Init()
{
var thr = new Thread(
async () =>
{
BusyContent = "Loading...";
try
{
// pre-init dx
await Application.Current.Invoke(
() =>
{
var window = new Views.ProductsPricesWindow();
window.Close();
}
);
...
}
finally
{
NotBusy();
}
}
);
thr.Name = nameof(Init);
thr.Start();
}
public async Task AddProductsPrices(Window p)
{
try
{
BusyContent = "Loading...";
await Task.Delay(100);
var window = new ProductsPricesWindow();
window.Show();
}
finally
{
NotBusy();
}
}
Application.Current.Invoke is extender:
public async static Task Invoke(this Application app, Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
if (app != null)
{
await app.Dispatcher.BeginInvoke(
priority,
action
);
}
else
action();
}
XAML
<extToolkit:BusyIndicator
IsBusy="{Binding IsBusy, Mode=OneWay}"
>
<extToolkit:BusyIndicator.BusyContent>
<TextBlock
Text="{Binding BusyContent}"
FontSize="72"
/>
<Grid>
...
</Grid>
</extToolkit:BusyIndicator.BusyContent>
</extToolkit:BusyIndicator>

Related

Adding Click Event Listener to Adobe Data Layer in React

I'm just starting to enter the world of Adobe Analytics. I'm working in React with Typescript and I am trying to leverage the adobe data layer to send information to Adobe Launch. I've been able to successfully use the adobe push function (i.e. window.adobeDataLayer.push({ test: 'test succeeded' })) and can both see that in the console via window.adobeDataLayer.getState() and ran a simple test to confirm it made its way to Adobe Launch.
However, when it comes to adding an event listener, I'm stumped. I attempted to follow Adobe's Documentation and came up with the following (doStuff() was just to confirm that eventListeners were working as expected, which they do):
function myHandler(event: any): void {
console.log("My handler was called")
}
function doStuff() {
console.log('do stuff was called')
}
function adobeAnalyticsTest(): void {
console.log(' adobeAnalyticsTest function called')
window.adobeDataLayer = window.adobeDataLayer || []
window.adobeDataLayer.push({ test: 'test succeeded' })
window.addEventListener('click', doStuff)
window.adobeDataLayer.push(function (dl: any) {
dl.getState()
dl.addEventListener('click', myHandler)
})
}
useEffect(() => {
adobeAnalyticsTest()
}, [])
Looking at window.adobeDataLayer, I couldn't see anything that seemed to indicate there was a click event listener (although this could be ignorance on my part) nor was 'My Handler was called' ever logged to the console. Does anyone have any ideas as to what I'm doing wrong or know how to tell when it's working correctly?
At face value, it looks like you don't have anything in your code that actually calls adobeAnalyticsTest().
Also, the listener you attach to dl doesn't listen for DOM events like clicking on something in window (like your window.addEventListener line); it listens for payloads pushed to adobeDataLayer where the passed event property value matches what you are listening for.
For example, put adobeDataLayer.push({'event':'click'}) in your js console you should see "My handler was called".
Think of it more like subscribing to a CustomEvent (because that's what it is, under the hood), rather than a native DOM event.

Is it possible to queue function calls in react?

I'm having a page with a textbox on (Textfield from fluent UI). I want to update a server stored value every time the user changes the text. I do not want the user to click a separate 'Save' button since that will interfer with the workflow on the page.
Everytime the user changes the text an event is raised with the new text. Simply enough I catch the event and updates the value. However, an event is raised for every character entered creating a lot of events. And if the user types quickly enough, the server will raise an error saying that I can't update the value cause there is already an update going on.
Is there a way to queue function calls? Meaning that I put the changes in a queue and a separate function handles them one after another?
<TextField
value={info.location}
onChange={locationChanged}
/>
const locationChanged = React.useCallback(
(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {
// update server here
},
[]
);
You are probably looking for a debounce function. This function will cancel the call if time between previous call and current call is less than a given time.
function debounce(func, timeout = 300){
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
const debounceChange = debounce(locationChanged);
<TextField
value={info.location}
onChange={debounceChange}
/>

Unblock widget without lose focus

I have a focused widget and a blocked widget, when I unblock the blocked widget, the focus is stolen
Look this example (click once on the run button) : http://www.qooxdoo.org/current/playground/#%7B%22code%22%3A%22var%2520field%2520%253D%2520new%2520qx.ui.form.TextField()%253B%250A%250Avar%2520button%2520%253D%2520new%2520qx.ui.form.Button(%2522A%2520button%2522)%253B%250A%250Avar%2520blocker%2520%253D%2520new%2520qx.ui.core.Blocker(button)%253B%250A%250Athis.getRoot().add(field%252C%2520%257Btop%253A%252010%252C%2520left%253A%252010%257D)%253B%250Athis.getRoot().add(button%252C%2520%257Btop%253A100%252C%2520left%253A10%257D)%253B%250A%250Ablocker.block()%253B%250Afield.focus()%253B%250Ablocker.unblock()%253B%22%2C%20%22mode%22%3A%22ria%22%7D
I'm not quite sure that what you try to achieve is what the qx.ui.core.Blocker was intended for. The implementation does not take in account that the focus is moved during the block.
If you call the method block the code tries to determine the widget which was focused before the block is done and saves that widget, then sets the focus to the widget to be blocked and restores the focus to the saved widget when unblocking.
With a small addition in a subclass, you could avoid that focus save/restore:
qx.Class.define("qx.ui.core.MyBlocker",
{
extend : qx.ui.core.Blocker,
properties : {
backupActiveWidget :
{
check : "Boolean",
init : true
}
},
members : {
// overridden
_backupActiveWidget : function() {
if(this.getBackupActiveWidget() === false) {
return
}
this.base(arguments);
}
}
});
and use the blocker this way:
var blocker = new qx.ui.core.MyBlocker(widgetToBeBlocked);
blocker.setBackupActiveWidget(false);
This should prevent the blocker from stealing the focus which may be set during the block.

electron + react + redux communication between two windows freezes PC?

I'm trying to update state in another window depedning on state changes in the first window but ipc communcation doesn't work as expected for me.
In my first window, I have:
onStatsSelect(event, menuItem, index) {
const selectedStatsCopy = this.state.selectedStats.slice();
const itemIndex = selectedStatsCopy.indexOf(menuItem.props.primaryText);
if (itemIndex > -1) {
delete selectedStatsCopy[itemIndex];
} else {
selectedStatsCopy[index] = menuItem.props.primaryText;
}
// Update state
this.setState({ selectedStats: selectedStatsCopy });
// Notify worker window of change
if (!(this.props.isReport)) {
console.log('in renderer main window');
ipcRenderer.send("updateSelectedStatsMain", event, selectedStatsCopy);
}
}
This is a callback for updating selectedStats state. It will also notify worker window of this update as seen in the last lines. if (!(this.props.isReport)) This is an important check since both windows share the same component and so I use property isReport to distinguish between the two.
In my main process, I have:
// Selected Stats have changed in main window
ipcMain.on('updateSelectedStatsMain', (event, selectedStatsCopy) => {
console.log('in main');
// Send to worker window
workerWindow.webContents.send('updateSelectedStatsRen', event, selectedStatsCopy);
});
This code will communicate back to worker window with the new state selectedStatsCopy.
In my comoponentDidMount, I have:
componentDidMount() {
// Register listening to message in case this is the worker window
if (this.props.isReport) {
console.log('in renderer worker window');
ipcRenderer.on("updateSelectedStatsRen", (event, selectedStatsCopy) => {
console.log('in renderer worker window event');
this.setState({ selectedStats: selectedStatsCopy });
});
}
}
This is supposed to work but electron hangs at line ipcRenderer.send("updateSelectedStatsMain", event, selectedStatsCopy); making main window hangs for a while and it continues using resources until PC freezes.
What is the problem here?
I figured out the error which I still don't know why it did freeze electron.
Basically I was doing
ipcRenderer.send("updateSelectedStatsMain", event, selectedStatsCopy);
This make absolutely no sense since I'm passing the event as a parameter. I don't even have an event variable to pass.
Updating this:
ipcRenderer.send("updateSelectedStatsMain",event, selectedStatsCopy);
to this:
ipcRenderer.send("updateSelectedStatsMain", selectedStatsCopy);
and this:
workerWindow.webContents.send('updateSelectedStatsRen', event, selectedStatsCopy);
to this:
workerWindow.webContents.send('updateSelectedStatsRen', selectedStatsCopy);
fxied the issue for me.

InteractionRequest<Confirmation>: the focus stays on the clicked button

Would anybody know how do I give the focus to the Cancel button of a Prism InteractionRequest?
What happens at the moment, is that given you've pressed enter on a button in a main window and the InteractionRequestTrigger fired, if you press enter again, you are in trouble as the focus stayed on the parent window and InteractionRequestTrigger fires a second time...
Thanks for your help
Focus on Confirmation dialog should be set automatically after firing the InteractionRequestTrigger.
You may find helpful the following State-Based Navigation Prism Quickstart:
State-Based Navigation
A Raise() method is invoked in the QuickStart for showing the InteractionRequest's dialog at ChatViewModel's SendMessage() method by setting the proper ViewModel context and its callback:
public IInteractionRequest SendMessageRequest
{
get { return this.sendMessageRequest; }
}
public void SendMessage()
{
var contact = this.CurrentContact;
this.sendMessageRequest.Raise(
new SendMessageViewModel(contact, this),
sendMessage =>
{
if (sendMessage.Result.HasValue && sendMessage.Result.Value)
{
this.SendingMessage = true;
this.chatService.SendMessage(
contact,
sendMessage.Message,
result =>
{
this.SendingMessage = false;
});
}
});
}
In addition, the ChatView detects the interaction request and the PopupChildWindowAction displays the SendMessageChildWindow pop-up window:
<prism:InteractionRequestTrigger SourceObject="{Binding SendMessageRequest}">
<prism:PopupChildWindowAction>
<prism:PopupChildWindowAction.ChildWindow>
<vs:SendMessageChildWindow/>
</prism:PopupChildWindowAction.ChildWindow>
</prism:PopupChildWindowAction>
</prism:InteractionRequestTrigger>
I hope this helps.
Ah! I should have marked my Popup as Focusable!

Resources