how to live update box field in DM - dialog

I would like a create a box with the field being update by another function (a loop in my example).
Each time value changes, the display should change.
My first attempt create a box which displays only the last value.
Class MyTestDialog: UIFrame
{
TagGroup CreateMyDialog(Object self)
{
TagGroup DialogTG = DLGCreateDialog("useless text")
TagGroup Fields,FieldItems
Fields = DLGCreateBox("Current",FieldItems)
FieldItems.DLGAddElement(DLGCreateRealField(42,15,3).DLGIdentifier("#RField"))
DialogTG.DLGAddElement(Fields.DLGTableLayOut(3,1,0))
Return DialogTG
}
Void Doit(Object self,number count)
{
for (count=0; count<5; count++)
{
self.LookUpElement("#RField").DLGValue(sum(getfrontimage())*count)
self.Display("Text at top of box window")
sleep(3)
}
}
Object Init(Object self) return self.super.Init(self.CreateMyDialog())
}
Object MyBeamCurrentDisplay = Alloc(MyTestDialog).Init()
MyBeamCurrentDisplay.Display("Text at top of box window")
sleep(3)
MyBeamCurrentDisplay.Doit(5)

Your script actually works (except you don't want to re-display the dialog each time.), but because both the DM-script itself, as well as UI updating calls are handled on DM's main thread, you don't see an updated.
The dialog gets updated 5times, but the display of the dialog only gets updated once your script has finished, giving the main-thread CPU cycles to handle the UI update.
If you put your script on a background-thread, you will see it works:
// $BACKGROUND$
Class MyTestDialog: UIFrame
{
TagGroup CreateMyDialog(Object self)
{
TagGroup DialogTG = DLGCreateDialog("useless text")
TagGroup Fields,FieldItems
Fields = DLGCreateBox("Current",FieldItems)
FieldItems.DLGAddElement(DLGCreateRealField(42,15,3).DLGIdentifier("#RField"))
DialogTG.DLGAddElement(Fields.DLGTableLayOut(3,1,0))
Return DialogTG
}
Void Doit(Object self,number count)
{
for (count=0; count<5; count++)
{
self.LookUpElement("#RField").DLGValue(sum(getfrontimage())*count)
// self.Display("Text at top of box window")
sleep(0.1)
}
}
Object Init(Object self) return self.super.Init(self.CreateMyDialog())
}
Object MyBeamCurrentDisplay = Alloc(MyTestDialog).Init()
MyBeamCurrentDisplay.Display("Text at top of box window")
sleep(0.2)
MyBeamCurrentDisplay.Doit(20)
(The first line of the code has to be exactly // $BACKGROUND )
An alternative way is to keep the script on the main thread, but add doEvents() before your sleep(0.1) to give some CPU cycles to the main application finishing queued task like updating UI's.
I also played a bit with updates and different threads and thought you might find the following example useful:
number TRUE = 1
number FALSE = 0
Class MyTestDialog: UIFrame
{
object messageQueue
object stoppedSignal
number updateTimeSec
void OnUpdateStateChanged(object self, taggroup checkboxTG )
{
if ( TRUE == checkboxTG.DLGGetValue() )
self.StartThread( "RegularFrontImageUpdate" )
else
messageQueue.PostMessage( Alloc(object) ) // Single message: Stop, so any object will do
}
number AboutToCloseDocument( object self, number verify )
{
result("\nAbout to close")
messageQueue.PostMessage( Alloc(object) ) // Single message: Stop, so any object will do
stoppedSignal.WaitOnSignal( infinity(), NULL ) // Ensure we don't kill the UI object while the update thread is still accessing it
return FALSE
}
TagGroup CreateMyDialog(Object self)
{
TagGroup DialogTG = DLGCreateDialog("useless text")
TagGroup Fields,FieldItems
Fields = DLGCreateBox("Current Front Image Sum",FieldItems)
FieldItems.DLGAddElement(DLGCreateStringField("No front image",30).DLGIdentifier("#StrField").DLGEnabled(FALSE))
FieldItems.DLGAddElement(DLGCreateRealField(0,15,3).DLGIdentifier("#RField").DLGEnabled(FALSE))
FieldItems.DLGAddElement(DLGCreateCheckbox("Update", TRUE, "OnUpdateStateChanged").DLGIdentifier("#ToggleCheck"))
DialogTG.DLGAddElement(Fields.DLGTableLayOut(3,1,0))
Return DialogTG
}
void RegularFrontImageUpdate(Object self)
{
stoppedSignal.ResetSignal()
self.SetElementIsEnabled("#RField",TRUE)
self.SetElementIsEnabled("#StrField",TRUE)
result("\nEnabling auto-update")
while(TRUE)
{
object message = messageQueue.WaitOnMessage( updateTimeSec, null )
if ( message.ScriptObjectIsValid() )
{
// Handle message. We just use 'empty' objects in this example
// as we only have a "single" message: Stop updating now!
self.LookUpElement("#ToggleCheck").DLGValue( 0 )
self.LookUpElement("#RField").DLGValue( 0 )
self.SetElementIsEnabled("#RField",FALSE)
self.SetElementIsEnabled("#StrField",FALSE)
result("\nDisabling auto-update")
break;
}
else
{
// Timeout of waiting time. Just update
image front := FindFrontImage()
if ( front.ImageIsValid() )
{
self.LookUpElement("#RField").DLGValue( sum(front) )
self.LookUpElement("#StrField").DLGValue( front.GetName() )
}
else
self.LookUpElement("#ToggleCheck").DLGValue( 0 )
}
}
stoppedSignal.SetSignal()
}
object Launch(object self)
{
updateTimeSec = 0.1
messageQueue = NewMessageQueue()
stoppedSignal = NewSignal( false )
self.Init(self.CreateMyDialog()).Display("Title")
self.StartThread( "RegularFrontImageUpdate" )
return self
}
}
Alloc(MyTestDialog).Launch()

Related

Dialog panels do not hide properly on switch - is there a workaround?

I have a dialog that contains two panels as shown below, created with the given code. As one can see in the right image, the second panel still contains content from the first panel.
Is there any workaround to fix that?
I feel like bugs like these should be mentionend on Stackoverflow.
class TestDialog : UIFrame{
TagGroup panel_list;
/**
* Switch to the `index`-th panel.
*/
void switchToPanel(object self, number index){
panel_list.DLGValue(index);
}
void switchToPanel0(object self){self.switchToPanel(0);}
void switchToPanel1(object self){self.switchToPanel(1);}
/**
* Create the dialog content
*/
TagGroup createContent(object self){
panel_list = DLGCreatePanelList(0);
TagGroup box, switch_button, input, panel;
// panel 1
box = DLGCreateBox("Panel 1");
// switch panel button
switch_button = DLGCreatePushButton("Switch to panel 2", "switchToPanel1");
box.DLGAddElement(switch_button);
// input field
input = DLGCreateStringField("ABC");
box.DLGAddElement(input);
panel = DLGCreatePanel();
panel.DLGAddElement(box);
panel_list.DLGAddElement(panel);
// panel 2
box = DLGCreateBox("Panel 2");
// switch panel button
switch_button = DLGCreatePushButton("Switch to panel 1", "switchToPanel0");
box.DLGAddElement(switch_button);
// add a label so both boxes have different heights
box.DLGAddElement(DLGCreateLabel(""));
// input field
input = DLGCreateStringField("DEF");
box.DLGAddElement(input);
panel = DLGCreatePanel();
panel.DLGAddElement(box);
panel_list.DLGAddElement(panel);
TagGroup wrapper = DLGCreateGroup();
wrapper.DLGAddElement(panel_list);
return wrapper;
}
object init(object self){
return self.super.init(self.createContent())
}
}
alloc(TestDialog).init().pose()
Fix: Hide manually
After a lot of trying out I think triggering the UIFrame::SetElementIsShown() manually fixes this issue. In the given example one can add the identifiers input0 to the first and input1 to the second input and then change the TestDialog::switchToPanel() function to the following:
/**
* Switch to the `index`-th panel.
*/
void switchToPanel(object self, number index){
panel_list.DLGValue(index);
if(index == 0){
self.setElementIsShown("input0", 1);
self.setElementIsShown("input1", 0);
}
else{
self.setElementIsShown("input0", 0);
self.setElementIsShown("input1", 1);
}
}
The complete code is then:
class TestDialog : UIFrame{
TagGroup panel_list;
/**
* Switch to the `index`-th panel.
*/
void switchToPanel(object self, number index){
panel_list.DLGValue(index);
if(index == 0){
self.setElementIsShown("input0", 1);
self.setElementIsShown("input1", 0);
}
else{
self.setElementIsShown("input0", 0);
self.setElementIsShown("input1", 1);
}
}
void switchToPanel0(object self){self.switchToPanel(0);}
void switchToPanel1(object self){self.switchToPanel(1);}
/**
* Create the dialog content
*/
TagGroup createContent(object self){
panel_list = DLGCreatePanelList(0);
TagGroup box, switch_button, input, panel;
// panel 1
box = DLGCreateBox("Panel 1");
// switch panel button
switch_button = DLGCreatePushButton("Switch to panel 2", "switchToPanel1");
box.DLGAddElement(switch_button);
// input field
input = DLGCreateStringField("ABC");
input.DLGIdentifier("input0");
box.DLGAddElement(input);
panel = DLGCreatePanel();
panel.DLGAddElement(box);
panel_list.DLGAddElement(panel);
// panel 2
box = DLGCreateBox("Panel 2");
// switch panel button
switch_button = DLGCreatePushButton("Switch to panel 1", "switchToPanel0");
box.DLGAddElement(switch_button);
// add a label so both boxes have different heights
box.DLGAddElement(DLGCreateLabel(""));
// input field
input = DLGCreateStringField("DEF");
input.DLGIdentifier("input1");
box.DLGAddElement(input);
panel = DLGCreatePanel();
panel.DLGAddElement(box);
panel_list.DLGAddElement(panel);
TagGroup wrapper = DLGCreateGroup();
wrapper.DLGAddElement(panel_list);
return wrapper;
}
object init(object self){
return self.super.init(self.createContent())
}
}
alloc(TestDialog).init().pose()
Workaround: Use Tabs
I also found out that tabs work with the exact same code. So if possible one can just replace the panels with tabs.
class TestDialog : UIFrame{
TagGroup tab_list;
TagGroup inputs;
/**
* Create the dialog content
*/
TagGroup createContent(object self){
inputs = NewTagList();
tab_list = DLGCreateTabList(0);
TagGroup box, input, tab;
// panel 1
box = DLGCreateBox("Panel 1");
// input field
input = DLGCreateStringField("ABC");
box.DLGAddElement(input);
// save the input field in a TagList, this creates the problem
inputs.TagGroupInsertTagAsTagGroup(infinity(), input);
tab = DLGCreateTab("Tab 1");
tab.DLGAddElement(box);
tab_list.DLGAddElement(tab);
// panel 2
box = DLGCreateBox("Panel 2");
// add a label so both boxes have different heights
box.DLGAddElement(DLGCreateLabel(""));
// input field
input = DLGCreateStringField("DEF");
box.DLGAddElement(input);
inputs.TagGroupInsertTagAsTagGroup(infinity(), input);
tab = DLGCreateTab("Tab 2");
tab.DLGAddElement(box);
tab_list.DLGAddElement(tab);
TagGroup wrapper = DLGCreateGroup();
wrapper.DLGAddElement(tab_list);
return wrapper;
}
object init(object self){
return self.super.init(self.createContent())
}
}
object dialog = alloc(TestDialog).Init();
dialog.pose();

Gtk Widgets returning None even when they hold data

I have a filechoosernative and a comboboxtext in my UI. Now I am trying to extract data from those two inside callbacks but they are returning me None even though they clearly have data set by the user. Why is this happening?
Excerpt from https://gitlab.com/9898287/nixwriter/-/blob/rir/src/frontend/mod.rs#L41
fn get_selected_file(&self) -> Option<std::path::PathBuf> {
let selected_file = self.fcn.get_filename();
dbg!(&selected_file);
selected_file
}
Excerpt from https://gitlab.com/9898287/nixwriter/-/blob/rir/src/frontend/mod.rs#L35
fn get_selected_device(&self) -> Option<udisks::DiskDevice> {
// Combo box text only stores a Gstring (Device ID)
// Search through the list of devices from udisks2 again
// and find the device with matching device ID
let selected_device = match self.lsblk_cbt.get_active_text() {
Some(txt) => {
dbg!(&txt);
for disk in crate::aux::backend::get_disks() {
if disk.drive.id == txt {
return Some(disk);
}
}
dbg!("No matching device found. Must reload.");
None
}
None => {
dbg!("lsblk_cbt is returning nothing");
None
}
};
dbg!(&selected_device);
selected_device
}
Both return None in https://gitlab.com/9898287/nixwriter/-/blob/rir/src/frontend/mod.rs#L110
fn set_lsblk_cbt(&mut self) {
let cbt = self.lsblk_cbt.clone();
for ddev in crate::aux::backend::get_disks() {
cbt.append_text(&ddev.drive.id);
}
let (device_chosen, file_chosen) = (
self.get_selected_device().is_some(),
self.get_selected_file().is_some(),
);
let start = self.start.clone();
cbt.connect_changed(move |_| {
start.set_sensitive(device_chosen && file_chosen);
dbg!("From set_lsblk_cbt", device_chosen, file_chosen);
});
}
even after the user has set a file and selected an item from ComboboxText.

Using CoreData Objects in a List as Environment Object

I'm currently creating a News-Feed-Reader App with SwiftUI.
I'm fetching the feed-items and storing them in CoreData
I'd like to display the objects in a List containing NavigationLinks to a Detail View and automatically mark them as read when clicking on them.
I'm currently fetching the objects and putting them in a ObservableObject.
This is the ObservableObject class:
final class FeedItem: ObservableObject, Hashable {
static func == (lhs: FeedItem, rhs: FeedItem) -> Bool {
return lhs.item.pubDate == rhs.item.pubDate && lhs.item.articleUrl == rhs.item.articleUrl
}
func hash(into hasher: inout Hasher) {
hasher.combine(item.articleUrl)
}
let objectWillChange = PassthroughSubject<Void, Never>()
init(item: NewsItem) {
self.item = item
}
// NewsItem is the Managed Object
var item: NewsItem
var bookmarked: Bool {
set {
//This function fetches the Object and marks it as bookmarked
DatabaseManager().markAs(item: item, .read, newValue)
self.item.bookmarked = newValue
}
get {
self.item.bookmarked
}
}
var read: Bool {
set {
//This function fetches the Object and marks it as read
DatabaseManager().markAs(item: item, .read, newValue)
self.item.read = newValue
}
get {
self.item.read
}
}
}
In the moment Im creating an environment Object (EO) containing an array of all ObservableObjects
This EO is passed down to the list and whenever Im clicking on an item Im setting its read value to true thereby changing the Core Data Object.
This is the list:
#EnvironmentObject var feed: // THe array of ObservableObjects
List() {
ForEach(feed.items.indices, id:\.self) { i in
Button(action: {
self.feed.items[i].read = true
self.selectedItem = i
self.showDetail = true
}) {
ListFeedItem(item: self.$feed.items[i])
}
}
}
This method is quite slow. Whenever I'm opening the Detail View and going back a few seconds later the List-Item takes multiple seconds to refresh.
Any ideas on how I could improve this?

How to create a blinking element in a UI frame

I have a nice UIframe dialog and would like to have a button that when pressed causes an element (i.e., small image) in the UI to blink. Pressing a second button should stop the element from blinking. Any example code available?
Thank you for your help.
There is no specific animated UI element available in DM scripting. However, I was successful with creating a 'blinking' element by using a periodic main thread to regularly alternate a bitmap element.
Here is the example code:
Class myBlinkDotDLG : UIframe
{
number onBlink
number periodicTaskID
image GetDotImage( object self, number highlight )
{
image dot := realimage( "Dot", 4,40,40)
dot = iradius < 20 ? (20-iradius) : 0
dot /= max(dot)
RGBImage colDot = highlight ? (RGB(255,180,0)*dot) : (RGB(250,80,0)*dot)
colDot = RGB( Red(colDot)+75,Green(colDot)+76,Blue(colDot)+78)
return ColDot
}
void StartBlink( object self )
{
if ( 0 == periodicTaskID )
periodicTaskID = AddMainThreadPeriodicTask( self,"BlinkToggle", 0.5 )
}
void StopBlink( object self )
{
if ( 0 != periodicTaskID )
RemoveMainThreadTask( periodicTaskID )
periodicTaskID = 0
}
void BlinkToggle( object self )
{
onBlink = !onBlink
Result( "\n Blink:" + onBlink )
taggroup dotTG = self.LookUpElement("Dot")
if ( dotTG.TagGroupisValid())
dotTG.DLGGetElement(0).DLGBitmapData(self.GetDotImage(onBlink))
else
self.StopBlink() // Important! You need to unregister the mainthread task if there is no dialog anymore
}
object CreateAndShowDialog( object self )
{
TagGroup DLG, DLGitems
DLG = DLGCreateDialog( "Test", DLGitems )
DLGitems.DLGAddElement( DLGCreateGraphic(40,40).DLGAddBitmap( self.GetDotImage(1) ).DLGIdentifier("Dot") )
DLGitems.DLGAddElement( DLGCreateLabel( "Blinking\tDot" ))
DLG.DLGTableLayout(2,1,0)
self.Init( DLG ).Display( "Blinky" )
self.StartBlink()
return self
}
}
Alloc( myBlinkDotDLG ).CreateAndShowDialog()
Note that the registered periodic task will keep the UIframe object in scope, even if the dialog window is closed.
However, the LookupElement() command will not return a valid TagGroup when the dialog window no longer exists, so I have used this to check for this condition and automatically unregister the task, should it still be running.
My example code doesn't have a button to start/stop the blinking, but that would be straight forward to add. Just have the according action methods call StartBlink and StopBlink

How to present a view controller while performing background tasks?

I am using Parse.com and swift
I have an initial view controller that presents the parse.com login.
Once the login is complete and the objects are saved in the background I want to present my navigation controller's root view controller (first controller linked in the storyboard).
How is this done with all the asynchronous calls?
This is what I have but it jumps back to the login screen and doesn't
func signUpViewController(signUpController: PFSignUpViewController!, didSignUpUser user: PFUser!) {
currentUser = user as? User
currentUser!.isManager = false
var query = PFUser.query()
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
if objects.count == 1 {
currentUser!.isManager = true
}
currentUser?.saveInBackgroundWithBlock({ (success: Bool, error: NSError!) -> Void in
if success == false || error != nil {
println(error)
} else {
currentCompany = Company()
currentCompany!.companyName = "My Company"
currentCompany!.saveInBackgroundWithBlock({ (success: Bool!, error: NSError!) -> Void in
if success == false {
println(error)
}
})
}
})
}
dismissViewControllerAnimated(true, completion: { () -> Void in
self.performSegueWithIdentifier("jumpFromInitialToMessages", sender: self)
// let vc = MessagesViewController()
// self.navigationController?.presentViewController(vc, animated: true, completion: nil)
})
}
If you want to initiate a UI transition/update inside the completion handler, you should insert a dispatch back to the main queue inside the completion handler closure:
var query = PFUser.query()
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
// do something
// when done, do some UI update, e.g. perform segue to another scene
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("identifier", sender: self)
}
}
This is an example with a simple query, but the idea works with any asynchronously called completion handler closure: Just dispatch the UI update back to the main queue from within the closure.

Resources