Adding a wpf button created in background thread to Mainwindow - wpf

Here's my scenario : I have a simple wpf window with a button. When the user clicks on the button, I want to create another window (let's call it child window) and then create a wpf button on a background thread, add it to the child window and show the child window. Here's the code for this:
Button backgroundButton = null;
var manualResetEvents = new ManualResetEvent[1];
var childWindow = new ChildWindow();
manualResetEvents[0] = new ManualResetEvent(false);
var t = new Thread(x =>
{
backgroundButton = new Button { Content = "Child Button" };
childWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() => childWindow.MainPanel.Children.Add(backgroundButton)));
manualResetEvents[0].Set();
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
WaitHandle.WaitAll(manualResetEvents);
childWindow.ShowDialog();
When I call the ShowDialog(), I get this error "The calling thread cannot access this object because a different thread owns it.". I know that this error is because the button added to the child window is created on a background thread and hence we get this error. Question is: How do I get past this error and still have my button to be created on the background thread

You must use Dispatcher every time, when you want to access Parent's window from another thread. I see in action of your thread , you use backgroundButton . Because of that, you must do whatever with your button inside Dispathcer.BeginIvoke statement
[EDIT]
Change Thread action to this
var t = new Thread(x =>
{
backgroundButton.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() => backgroundButton = new Button { Content = "Child Button" }));
childWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() => childWindow.MainPanel.Children.Add(backgroundButton)));
manualResetEvents[0].Set();
});
I wrote this according to your code.I doesn't check you code, but hope its right.

Related

Command handling in Qooxdoo multi window application

I want to create a Qooxdoo application that consists of a Desktop with multiple Windows. The Desktop (and not each Window) also has a (common) ToolBar.
Now I want to have a command to "save" the document of the active window. This command can be triggered by the keyboard shortcut "Ctrl+S" as well as a button on the toolbar.
To handle the "Ctrl+S" to reach the currently active window the qx.ui.command.GroupManager (as described by https://qooxdoo.org/documentation/v7.5/#/desktop/gui/interaction?id=working-with-commands and https://qooxdoo.org/qxl.demobrowser/#ui~CommandGroupManager.html) is supposed to be the best solution. And I could easily implement that in my code.
But now I'm struggling to make the toolbar save button also call the save command of the currently active window as I don't know how to bind it correctly to the GroupManager.
An example code to get started in the playground https://qooxdoo.org/qxl.playground/:
// NOTE: run this script only once. Before running it again you need to reload
// the page as it seems that the commands are accumulation and not reset.
// I guess that this is a bug in the Playground
const root = this.getRoot();
qx.Class.define('test.Application',
{
extend : qx.application.Standalone,
members: {
main: function() {
const layout = new qx.ui.layout.VBox(5);
const container = new qx.ui.container.Composite(layout);
root.add(container, {edge: 0});
const windowManager = new qx.ui.window.Manager();
const desktop = new qx.ui.window.Desktop(windowManager);
this._manager = new qx.ui.command.GroupManager();
const menuBar = new qx.ui.menubar.MenuBar();
let menu = new qx.ui.menu.Menu();
///////////////////////////
// TODO: Call _doSave() of the active window!
let saveMenuButton = new qx.ui.menu.Button('Save','#MaterialIcons/save/16');
///////////////////////////
menu.add(saveMenuButton);
var fileMenu = new qx.ui.menubar.Button('File', null, menu);
menuBar.add(fileMenu);
const toolBar = new qx.ui.toolbar.ToolBar();
///////////////////////////
// TODO: Call _doSave() of the active window!
let saveToolBarButton = new qx.ui.toolbar.Button('Save','#MaterialIcons/save/16');
///////////////////////////
toolBar.add(saveToolBarButton);
container.add(menuBar,{flex:0});
container.add(toolBar,{flex:0});
container.add(desktop,{flex:1});
this._foo1 = new test.Window('foo1', this);
desktop.add(this._foo1);
this._foo1.open();
this._foo1.moveTo(100,20);
this._foo2 = new test.Window('foo2', this);
desktop.add(this._foo2);
this._foo2.open();
this._foo2.moveTo(200,100);
this._foo3 = new test.Window('foo3', this);
desktop.add(this._foo3);
this._foo3.open();
this._foo3.moveTo(300,180);
},
getGroupManager() {
return this._manager;
}
}
});
qx.Class.define('test.Window', {
extend: qx.ui.window.Window,
construct(windowName, controller) {
this.base(arguments, windowName);
this._name = windowName;
let commandGroup = new qx.ui.command.Group();
const cmd = new qx.ui.command.Command("Ctrl+S");
cmd.addListener('execute', this._doSave, this);
commandGroup.add('save', cmd);
controller.getGroupManager().add(commandGroup);
this.addListener('changeActive', () => {
if (this.isActive()) {
controller.getGroupManager().setActive(commandGroup);
}
}, this);
},
members: {
_doSave() {
alert("save " + this._name);
}
}
});
a = new test.Application();
How should the saveMenuButton.setCommand() and saveToolBarButton.setCommand() should look like to always call the command of the active window?
You can control a current active window via Desktop class:
let saveToolBarButton = new qx.ui.toolbar.Button('Save');
saveToolBarButton.addListener("click", function(){
desktop.getActiveWindow()._doSave();
}, this);
Would be great for your solution imo is to create a separate command and add this command to buttons:
const saveActiveWindowCommand = new qx.ui.command.Command();
saveActiveWindowCommand.addListener("execute", function(){
desktop.getActiveWindow()._doSave();
}, this);
let saveMenuButton = new qx.ui.menu.Button('Save');
saveMenuButton.setCommand(saveActiveWindowCommand);
let saveToolBarButton = new qx.ui.toolbar.Button('Save');
saveToolBarButton.setCommand(saveActiveWindowCommand);
EDIT:
You could set commands dynamically for "Main Panel" menu buttons. Because there is only one instance of command pressing "Ctrl+S" will trigger only one command but maybe you would like that main bar save buttons have extra logic.
You have in application class next method which will be called from window class when changeActive event happens.
setSaveCommand(command){
this.saveMenuButton.setCommand(command);
this.saveToolBarButton.setCommand(command);
},
and in your Window class:
if (this.isActive()) {
controller.setSaveCommand(cmd);
controller.getGroupManager().setActive(commandGroup);
}

GWT Mobile/Touch screen device - Auto hide PopupPanel with glass enabled triggers touch/click on underlying widgets when tapping on the glass

I am trying to use GWT PopupPanel (or DialogBox) with glass and autohide enabled to create a context actions menu on mobile devices, but I have an issue when the user whats to close the actions menu popup (by tapping on the glass of the popup, outside the content to trigger autohide): the underlying widgets ("beneath" the glass) also receive the tap event when the popup is closed. For example, if there is a button at that position that opens a new view/window, the button is clicked and executes his click handler.
My code:
public void onModuleLoad() {
Button button = new Button("Test");
button.addClickHandler(cl -> {
Label lb = new Label("This is the content");
lb.getElement().getStyle().setBackgroundColor("#fff");
lb.setSize("200px", "80px");
DialogBox pop = new DialogBox();
pop.setAutoHideEnabled(true);
pop.setGlassEnabled(true);
pop.setWidget(lb);
pop.center();
});
Button buttonBehindGlass = new Button("Test over");
buttonBehindGlass.addClickHandler(cl -> {
Window.alert("Action 2");
});
RootPanel.get().add(button);
RootPanel.get().add(buttonBehindGlass);
}
In this example, if you open the popup, then click/tap outside the content, on the glass, over the "buttonBehindGlass" widget, you will notice that the popup closes and "buttonBehindGlass" is clicked, at the same time.
Is there any way to avoid this?
I tested on Android and Chrome dev tools enabled with responsive/touch mode. This issue does not appear on desktop, everything is fine there.
Create your own DialogBox, based on the GWT one
public class DialogBoxExtended extends DialogBox {
#Override
protected void onPreviewNativeEvent(NativePreviewEvent event) {
super.onPreviewNativeEvent(event);
// check if the event does not target the popup
Event nativeEvent = Event.as(event.getNativeEvent());
if (!eventTargetsPopup(nativeEvent)) {
// on click, touch end, etc. close the dialog box
switch (event.getTypeInt()) {
case Event.ONMOUSEDOWN:
case Event.ONCLICK:
case Event.ONTOUCHEND:
hide();
break;
}
}
}
/**
* Does the event target this popup?
*
* #param event the native event
* #return true if the event targets the popup
*/
private boolean eventTargetsPopup(NativeEvent event) {
EventTarget target = event.getEventTarget();
if (Element.is(target)) {
return getElement().isOrHasChild(Element.as(target));
}
return false;
}
}
disable auto hide and use your created DialogBox
public void onModuleLoad() {
Button button = new Button("Test");
button.addClickHandler(cl -> {
Label lb = new Label("This is the content");
lb.getElement().getStyle().setBackgroundColor("#fff");
lb.setSize("200px", "80px");
DialogBox pop = new DialogBoxExtended();
pop.setAutoHideEnabled(false);
pop.setGlassEnabled(true);
pop.setWidget(lb);
pop.center();
});
Button buttonBehindGlass = new Button("Test over");
buttonBehindGlass.addClickHandler(cl -> {
Window.alert("Action 2");
});
RootPanel.get().add(button);
RootPanel.get().add(buttonBehindGlass);
}
It now works as expected.
With auto hide enabled the event is processed but left to be consumed by other handlers - that's why the event gets to the underlying button. On desktop it was not a problem because Event.ONMOUSEUP was being considered, but on mobile/touch mode the Event.ONTOUCHEND is being triggered instead.

how to detach a shortcut command on Qooxdoo?

I have this code:
qx.Class.define('my.Window', {
extend: qx.ui.window.Window,
construct: function(caption, icon) {
this.base(arguments, caption, icon);
this.setLayout(new qx.ui.layout.Basic());
this.__btn = new qx.ui.form.Button('Shortcut Test');
this.__cmd = new qx.ui.command.Command('Alt+T');
this.__cmd.addListener("execute", function() { alert('FOOBAR'); });
this.__btn.setCommand(this.__cmd);
this.add(this.__btn);
},
members: {
__btn: null,
__cmd: null
}
});
qx.Class.define('my.Compo', {
extend: qx.ui.container.Composite,
construct: function() {
this.base(arguments);
this.setLayout(new qx.ui.layout.HBox());
this.__btnShow = new qx.ui.form.Button("Show Window", "icon/22/apps/internet-web-browser.png");
this.__btnDestroy = new qx.ui.form.Button('Destroy window');
this.__btnNull = new qx.ui.form.Button('Null window');
this.__btnDestroy.addListener('execute', function(){
this.__window.destroy();
}, this);
this.__btnNull.addListener('execute', function(){
this.__window = null;
}, this);
this.__btnShow.addListener("execute", function(e){
if(this.__window) {
console.info('Window exist');
this.__window.open();
this.__window.center();
}
else {
console.info('Window do not exist!');
this.__window = new my.Window("Shortcut test window");
this.__window.setWidth(300);
this.__window.setHeight(200);
this.__window.setShowMinimize(false);
this.__window.open();
this.__window.center();
}
}, this);
this.add(this.__btnShow);
this.add(this.__btnDestroy);
this.add(this.__btnNull);
},
members: {
__btnShow: null,
__btnDestroy: null,
__window: null
}
});
var compo = new my.Compo();
this.getRoot().add(compo);
So, if you try "Alt+T" shortcut before clicking "Show Window" button, nothing happen. After show the Window, the shortcut is available and an alert is showed.
Well, the issue to me is the remaining availability of the shortcut in scenarios where that shortcut must do not exist any more:
When the window is close normally.
When Null window button is execute, and after that if you use "Alt+T" then the alert is showed twice, and so on as many time you switch between the Null and Show buttons.
Same behavior as (2) even if destroy() method of window is called explicitly.
On playground if "Run" button is clicked several times, also equal times is showed alert after use the shortcut.
Thanks no all for your time. :)
On Playground
The qx.ui.command.Command wraps the qx.bom.Shortcut that attaches 2 listeners to document element. When you close the window qx.ui.command.Command instance was not set inactive or destroyed. You'll have to handle the window close event properly.
destruct: function()
{
this.__cmd = null;
}
it does not destroy the command. If you try:
qx.core.ObjectRegistry.fromHashCode("the command object hash code").getActive()
You will find that it exists and is active. You forgot to destroy the command calling
this.__cmd.dispose()
Qooxdoo Playground App object registry is not initialized every time someone pushes "Run". So, qooxdoo object's lifetime is bound to page lifetime or a disposal event.

freeze wpf app during update binding source

I created thumbnails based on ListView control. On ListView.ItemSource I bind ObservableColletion<Photos> Photos{get;set}.
I create thumbnail images in another threads also in parallel way.
I simplified my code.
public class ThumbnailCreator
{
public static List<Photos> CreateThumbnailImage(List<Photos> photos)
{
var thumbnails = new List<Photos>();
Parallel.ForEach(photos, photo =>
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.DecodePixelHeight = 128;
bitmap.DecodePixelWidth = 128;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.CreateOptions = BitmapCreateOptions.DelayCreation;
bitmap.UriSource = new Uri(photo.FullPath);
bitmap.EndInit();
if (bitmap.CanFreeze)
bitmap.Freeze();
thumbnails.Add(new Photos{ThumbnailImage = bitmap});
});
return thumbnails;
}
}
Problem is here:
//I break binding before I start refreshing thumbnails
this.Thumbnails.ItemsSource = null;
//load files from folder
List<Photos> photos = _fileManager.LoadFromDir(folderBrowserDialog.SelectedPath);
//create thumbnail images in another threads, not on UI
List<Photos> thumbnails = ThumbnailCreator.CreateThumbnailImage(photos);
//create new source
Photos = new ObservableCollection<Photos>(thumbnails);
//reenable binding, this part of code cause that UI free
this.Thumbnails.ItemsSource = Photos;
When I reenable binding UI freeze, I tried use dispatcher but result is same UI freeze.
this.Dispatcher.Invoke(DispatcherPriority.SystemIdle, new Action(() =>
{
Photos = new ObservableCollection<Photos>(thumbnails);
this.Thumbnails.ItemsSource = Photos;
}));
How can I avoid freeze UI?
EDITED:
I edited my code based on Dean K. advice. I dont break bind before update source of listview.
I updated source of ListView.ItemSource via Dispatcher:
Sync Invoke:
App.Current.Dispatcher.Invoke(new Action(() =>
{
thumbnails.Add(new Photos { ThumbnailImage = bitmap });
}), DispatcherPriority.ContextIdle);
Result - UI behavior.
Images are being added continuously but if collection contains more than 500 images at the and WPF window freezes. For example it is not possible move window, scroll listview.
Async Invoke
App.Current.Dispatcher.InvokeAsync(new Action(() =>
{
thumbnails.Add(new Photos { ThumbnailImage = bitmap });
}), DispatcherPriority.ContextIdle);
Result - UI behavior.
At start app freezes but after several seconds images are being added continously and also is it possible move window, scroll listview.
So my question is what is root of problem that app freezes ? How can I avoid this behaviour. I upload sample application.
Don't break the binding before adding items to the ObservableCollection.
Bind Thumbnails.ItemsSource to Photos OC and than on another thread load and add items to Photos OC. That way your UI will not freeze.
You might want to use the multithreaded ObservableColleciton you can find on code project. Search for ObservableCollectionMt...

Add a title to a new window in Titanium

I'm trying to open a new window via a button to the configuration page but how do you add a title (at the top of the screen) to a newly created window?
On the starting page, I have a button created to open a new page as:
var btnConfig = Ti.UI.createButton({
backgroundImage:'img/settings.png',
height:33,
width:33
});
win1.rightNavButton = btnConfig;
btnConfig.addEventListener('click',function(){
Ti.include('win_config.js');
})
Then on win_config.js :
var win_config = Titanium.UI.createWindow({
title:"Configure",
backgroundColor:'#BBB',
});
win_config.open({
transition:Ti.UI.iPhone.AnimationStyle.FLIP_FROM_LEFT
});
I thought that by just setting "title" it'll appear but apparently it doesnt.
needed to add "modal:true" in the createWindow
var win_config = Titanium.UI.createWindow({
title:"Configure",
backgroundColor:'#BBB',
modal:true
});

Resources