11-06-2013 03:45 PM
I've read here and here for help. The best I can deduce is to call SetSystemAttribute and change the ATTR_DEFAULT_MONITOR value before doing a LoadPanel followed by a DisplayPanel. Then to change the panel from that monitor to another, I must call a DiscardPanel, then repeat the whole process.
This successfully changes my panel from one to a second monitor.
However, as soon as I discard the panel, all the control data on that panel gets wiped clean. So how to change a panel from monitor to monitor without losing its data?
According to the help:
To open panels on another monitor, call SetSystemAttribute to change the value of ATTR_DEFAULT_MONITOR before you call LoadPanel or DisplayPanel.
I have found this not exactly to be the case. Repeat calling of the DisplayPanel just redraws the panel. Again, I'd have to close the panel first. I tried a HidePanel instead, but that's not working.
On my panel, I have a table of data. I need this to stay resident while switching the panel between monitors. Ideas?
Solved! Go to Solution.
11-06-2013 05:03 PM - edited 11-06-2013 05:06 PM
I have no experience with multi monitor systems, but I suppose you could rely on SavePanelState and RecallPanelState to properly handle transferring data from the old to the new panel. Be aware of the problem discussed in this thread: the thread is quite old and I don't know if this issue has been solved in recent versions of CVI; if not, you may need to store somewhere the number of lines in the old table control, then load the new panel, add the lines to the table and finally restore values in the new panel.
11-07-2013 08:55 AM
Thanks, Roberto. We are so close! Here is the code I'm using to test this. I have a switch control located on another panel which is not saved. I am trying to save my "mainPanel" and its table data.
case EVENT_COMMIT: // first preserve the data in the panel GetNumTableRows(mainPanel,MAIN_TABLE,&tableRows); SavePanelState(mainPanel,"backup.panel",0); // next trash the main panel DiscardPanel(mainPanel); // switch is configured for 0 = Here, 1 = There GetCtrlVal(panel,control,&switchVal); switch (switchVal) { case TRUE: // change monitor SetSystemAttribute(ATTR_DEFAULT_MONITOR, 1); mainPanel = LoadPanel(0, "Panels.uir", MAIN); DisplayPanel(mainPanel); InsertTableRows(mainPanel,MAIN_TABLE,-1,tableRows,VAL_USE_MASTER_CELL_TYPE); RecallPanelState(mainPanel,"backup.panel",0); break; case FALSE: // change monitor SetSystemAttribute(ATTR_DEFAULT_MONITOR, 0); mainPanel = LoadPanel(0, "Panels.uir", MAIN); DisplayPanel(mainPanel); InsertTableRows(mainPanel,MAIN_TABLE,-1,tableRows,VAL_USE_MASTER_CELL_TYPE); RecallPanelState(mainPanel,"backup.panel",0); break; } break;
Unfortunately, I get the NFRTE "The control values could not be loaded because the panel has changed". I have set up some breakpoints and observed that when the panel reloads on the new monitor, the correct number of rows gets inserted first.
As you can see, between the time I save the state and the time I recall it again, I'm not making any direct changes to the panel. What other activities qualify as changes? I have two timers on mainPanel, so I also tried disabling them before the SavePanelState. Still getting that error. :manfrustrated:
11-07-2013 09:27 AM
Yikes, I'm not the only one!
https://forums.ni.com/t5/LabWindows-CVI/Table-Control/m-p/1971737/highlight/true#M57627
I think I'm just going to save out each row of the table myself and repopulate the table after reloading the panel.
11-07-2013 09:45 AM
ElectroLund ha scritto:
I think I'm just going to save out each row of the table myself and repopulate the table after reloading the panel.
It could be faster to duplicate the control on a hidden panel and then duplicate it again on the new main panel.
(It's not an idea of mine: Luis has given it here)
11-07-2013 11:35 AM
Great suggestion, Roberto. This is a fast method! In case others are interested, here is the code I settled on:
int CVICALLBACK CallPanel (int panel, int control, int event, void *callbackData, int eventData1, int eventData2) { struct sourcedata source[MAX_TABLE_SOURCES]; int tempPanel; int tempTable = (int)callbackData; // retrieve previous control ID int switchVal; switch (event) { case EVENT_COMMIT: tempPanel = NewPanel(0,"Temporary Panel",VAL_AUTO_CENTER,VAL_AUTO_CENTER,500,1000); tempTable = DuplicateCtrl(mainPanel,tableControl,tempPanel,0,VAL_KEEP_SAME_POSITION,VAL_KEEP_SAME_POSITION); // save the duplicated control ID for next monitor switch SetCtrlAttribute(panel,control,ATTR_CALLBACK_DATA,(void *)tempTable); // next trash the main panel DiscardPanel(mainPanel); // switch is configured for 0 = Here, 1 = There GetCtrlVal(panel,control,&switchVal); SetSystemAttribute(ATTR_DEFAULT_MONITOR,switchVal); // reinit main panel and copy over table with updated control ID mainPanel = LoadPanel(0, "Panels.uir", MAIN); tableControl = DuplicateCtrl(tempPanel,tempTable,mainPanel,0,VAL_KEEP_SAME_POSITION,VAL_KEEP_SAME_POSITION); // now done with temp panel DiscardPanel(tempPanel); // redisplay main DisplayPanel(mainPanel); break; default: return 0; } return 0; }
The only caveat to this solution is that with deleting and recopying the table control each time, any other manipulation of the table control outside this function will need access to the control ID. So rather than have the table predefined in my UIR, I elected to simply create the control programmatically in my main init function:
tableControl = NewCtrl(mainPanel,CTRL_TABLE,"X-Ray Sources",87,62); SetCtrlAttribute(mainPanel,tableControl,ATTR_CALLBACK_FUNCTION_POINTER,*TableEdit);
tableControl is global, so it should be used for all other table references.
11-07-2013 01:19 PM
Correction on the above...
Using the ATTR_CALLBACK_FUNCTION_POINTER was incorrect. When I removed my existing table control from my UIR file, it was then necessary to create the callback function like this instead:
InstallCtrlCallback(mainPanel,tableControl,TableEdit,NULL);
05-09-2019 02:33 AM - edited 05-09-2019 02:34 AM
Old thread, but still useful, I try to add some information for a similar situation.
I have 2 touch monitor and for security reason, the software command a machine, i have to use only 1 monitor per time.
a solution that i've found on the web is this, swap the active monitor trough a windows command:
The built-in DisplaySwitch.exe tool can emulate the WindowsKey + P capabilities. You can potentially create four desktop shortcuts so that you have one available for each option: For the PC screen only: %windir%\System32\DisplaySwitch.exe /internal For Duplicate: %windir%\System32\DisplaySwitch.exe /clone For Extend: %windir%\System32\DisplaySwitch.exe /extend For Second screen only: %windir%\System32\DisplaySwitch.exe /external
the other solution that i've thinked is to SetPanelAttribute and put left coordinate with offset to go on other monitor.
int first_monitor = {0};
int num_monitor = {0};
int primary_monitor = {0};
GetSystemAttribute (ATTR_FIRST_MONITOR, &first_monitor);
GetSystemAttribute (ATTR_NUM_MONITORS, &num_monitor);
if(num_monitor > 1)
{
GetSystemAttribute (ATTR_PRIMARY_MONITOR, &primary_monitor);
GetMonitorAttribute (primary_monitor, ATTR_WIDTH, &width);
GetPanelAttribute (panel, ATTR_LEFT, &left);
if(left < width) SetPanelAttribute (panel, ATTR_LEFT, left + width);
else if(left >= width) SetPanelAttribute (panel, ATTR_LEFT, left - width);
}
05-17-2019 06:10 AM
Another (untested) method would be to:
- change the ATTR_DEFAULT_MONITOR
- create a temporary empty panel with CreatePanel with a Center on Load attribute, display it
- get its Left/Top
- discard it
- set the Left/Top of your panel to similar coordinates