In my program I want to create a menu with submenus based on a folder and its subfolders.
The first function menu_fromfiles works fine.
But the second function menu_fromfolders keeps giving me an error: submenu does not exist - and I can't figure it out. Any clues?
Here is my code:
#SingleInstance force
RButton::
menu_fromfolders("filelist", "File List", "c:\test", "*.txt", "thismenu")
menu, thismenu, show
Return
-------------------------------------------------------------------
menu_fromfolders(submenuname, menutitle, whatdir, filemask="*", parentmenu="", whatsub="DONOTATHING"){
global
loop, %whatdir%\*, 1, 0
{
if(file_isfolder(A_LoopFileFullPath)){
menu_fromfiles(a_loopfilename, a_loopfilename, A_LoopFileFullPath, filemask, "", whatsub)
menu, %submenuname%, add, %a_loopfilename%, :%a_loopfilename%
}else{
menu, %submenuname%, add, %a_loopfilename%, %whatsub%
}
}
if(parentmenu){
menu, %parentmenu%, add, %menutitle%, :%submenuname%
}
return submenuname
}
menu_fromfiles(submenuname, menutitle, whatdir, filemask="*", parentmenu="", whatsub="DONOTATHING"){
loop, %whatdir%\%filemask%, 0, 0
{
menu, %submenuname%, add, %a_loopfilename%, %whatsub%
}
if(parentmenu){
menu, %parentmenu%, add, %menutitle%, :%submenuname%
}
return submenuname
}
;returns true if the item is a folder, false if is a file
file_isfolder(whatfile){
lastchar := substr(whatfile, 0, 1) ;fetch the last character from the string
if(lastchar != "\")
whatfile := whatfile . "\"
if(fileexist(whatfile))
return true
}
;empty command for the menu title
DONOTATHING:
return
Here is a more detailed error message:
Error in ...script.ahk
Submenu does not exist
-->menu, %submenuname%, add, %a_loopfilename%, :%a_loopfilename%
[update]
Please see my solution below. You can find the complete and refined script here: http://www.autohotkey.com/board/topic/95219-dynamicfilemenuahk/
Ah, figured it out.
When the menu_fromfiles loop searches for files, if no files matching the mask are discovered, it does not add anything to the menu.
Now, you can't create a submenu with nothing in it. So if no items were added to the submenu, no submenu is created.
It was working intermittently only for directories that had folders containing files matching the mask. When there were no matching files (or the directory was empty), the function was trying to add a non-existent menu.
The solution is to add a method of detecting that a menuitem was, indeed created:
menu_fromfiles(submenuname, menutitle, whatdir, filemask="*", parentmenu="", whatsub="DONOTATHING"){
menucount := 0
loop, %whatdir%\%filemask%, 0, 0
{
menu, %submenuname%, add, %a_loopfilename%, %whatsub%
menucount++
}
if(parentmenu && menucount){
menu, %parentmenu%, add, %menutitle%, :%submenuname%
}
return submenuname
}
If anyone is interested, I refined this code to display all the folders. You will find the full script here: http://www.autohotkey.com/board/topic/95219-dynamicfilemenuahk/
Related
It has been 10 months since I worked on my app due to a death in the family, just started looking at it again and still not sure how to solve the problem.
The project inquires/help started here:
MFC MDI Collecting control states for the "apply" button routine
Since this is a specific focused question, I didn't want to muck up my other thread, so what I'd like to do is change the documents tab styles after the view is loaded. I know that this can be done because the master repository from Microsoft with all the code examples has a project called VCSamples-master\VCSamples-master\VC2010Samples\MFC\Visual C++ 2008 Feature Pack\TabControl which I have looked at. It dawns on me that even though I can follow its code, the calls are from within the MDI window itself where my issue is I'm trying to do this via a property page dialog using OnApply which changes things.
I was able to do part of this properly with the help of the thread above to the OutputPane successfully because I was able to get the Pane handle and execute. I was told that for the MDI tabs after creation that I need to parse the tabs, count them, and then execute. So my issue here is after I capture the tabs......how to change their styles.
Here is the code as it stands:
BOOL CSettingsUserTabs::OnApply()
{
BOOL bResult = CMFCPropertyPage::OnApply();
if (bResult)
{
// Update Output Pane Tab Styles (Works 100%)
AfxGetApp()->WriteProfileInt(_T("Settings"), _T("UserTabStyle"), m_style_tabs); // Save value to registry
((CMainFrame*)AfxGetMainWnd())->m_wndOutput.m_wndTabs.ModifyTabStyle((CMFCTabCtrl::Style)m_style_tabs);
((CMainFrame*)AfxGetMainWnd())->m_wndOutput.m_wndTabs.RecalcLayout();
//Get the open file tabs in the MDI
for (POSITION pos = AfxGetApp()->GetFirstDocTemplatePosition(); pos != NULL; )
{
CDocTemplate* pTempl = AfxGetApp()->GetNextDocTemplate(pos);
for (POSITION pos1 = pTempl->GetFirstDocPosition(); pos1 != NULL; )
{
CDocument* pDoc = pTempl->GetNextDoc(pos1);
for (POSITION pos2 = pDoc->GetFirstViewPosition(); pos2 != NULL; )
{
CView* pView = pDoc->GetNextView(pos2);
if (pView->IsKindOf(RUNTIME_CLASS(CTrainView)))
{
// THIS IS WHERE MY ISSUE IS, NOW THAT ALL THE TABS ARE
// CAPTURED, HOW DO I ADDRESS THEM LIKE WHAT IS SHOWN
// ABOVE:
//((CMainFrame*)AfxGetMainWnd())->xxxxxx.yyyyyy.ModifyTabStyle((CMFCTabCtrl::Style)m_style_tabs);
}
}
}
}
}
return bResult;
}
If I can figure this last piece out, I'll be basically finished, I just can't seem to find a solution on how to do this via property sheet via OnApply.
Any suggestions or actual code examples I can see to solve my problem?
FYI: No, I haven't had any time to take additional OOP to solve this. I'm hoping someone can provide some guidance so I can move on after getting this sorted.
Thanks,
Chris
EDIT 1:
So I took a closer look at Constantine's suggestion and here is what I came up with:
BOOL CSettingsUserTabs::OnApply()
{
BOOL bResult = CMFCPropertyPage::OnApply();
if (bResult)
{
// Update Output Pane Tab Styles
AfxGetApp()->WriteProfileInt(_T("Settings"), _T("UserTabStyle"), m_style_tabs); // Save value to registry
((CMainFrame*)AfxGetMainWnd())->m_wndOutput.m_wndTabs.ModifyTabStyle((CMFCTabCtrl::Style)m_style_tabs);
((CMainFrame*)AfxGetMainWnd())->m_wndOutput.m_wndTabs.RecalcLayout();
CMFCTabCtrl& MDI_STYLES = ((CMainFrame*)AfxGetMainWnd())->GetMDITabs();
MDI_STYLES.ModifyTabStyle((CMFCTabCtrl::Style)m_style_tabs);
MDI_STYLES.RecalcLayout();
CMDIFrameWndEx* pMainFrame = DYNAMIC_DOWNCAST(CMDIFrameWndEx, GetTopLevelFrame());
pMainFrame->SetFocus();
pMainFrame->RecalcLayout();
}
return bResult;
}
The m_styles_tabs is getting the index value of 0-8 when I select the radio button. The code compiles and runs and I see the index value change when I break on it, but the tabs for the MDI are still not updating. Does the edited code make sense based on the members shown here:
https://learn.microsoft.com/en-us/cpp/mfc/reference/cmfctabctrl-class?view=msvc-170#modifytabstyle
I think this the right direction, am I missing something?
I have a window which has a CTreeCtrl. A user can right-click any element and display a context menu. From there they can choose to delete the entry. Something like this:
Here is a snippet of the context menu item handler:
void CAssignHistoryDlg::OnDeleteFromAssignmentHistory()
{
CString strINI = theApp.GetAssignHistoryPath();
HTREEITEM hItem = m_treeHistory.GetSelectedItem();
CString strName, strDeletedName, strEntry;
if (m_treeHistory.GetParentItem(hItem) != nullptr)
{
// The user has picked one of the history dates.
// So the parent should be the actual name.
hItem = m_treeHistory.GetParentItem(hItem);
// Now OK to proceed
}
strName = ExtractName(hItem);
GetParent()->EnableWindow(FALSE);
strEntry.Format(IDS_TPL_SURE_DELETE_FROM_ASSIGN_HIST, strName);
if (AfxMessageBox(strEntry, MB_YESNO | MB_ICONQUESTION) == IDNO)
{
The image shows my problem. If I first click on Test, so that it is selected and bright blue and then right-click, it shows Test in the popup message. This is fine. But ...
If the first name is initially selected, and I go ahead and directly right-click Test, even though it seems to go blue (as if selected), m_treeHistory.GetSelectedItem() is returning the original, first name. I think I am describing it not very well.
In short, I want to guarantee that I get the HTREEITEM for the item that the user right-clicked on. What I have is not 100% fool-proof.
If it helps, this is how I display the context menu:
void CAssignHistoryDlg::OnNMRclickTreeHistory(NMHDR *pNMHDR, LRESULT *pResult)
{
CMenu mnuContext, *pMnuEdit = nullptr;
CPoint ptLocal;
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
GetCursorPos(&ptLocal);
mnuContext.LoadMenu( IDR_MENU_SM_ASSIGN_HIST_POPUP );
pMnuEdit = mnuContext.GetSubMenu( 0 );
if (pMnuEdit != nullptr)
{
pMnuEdit->TrackPopupMenu( TPM_LEFTALIGN|TPM_LEFTBUTTON,
ptLocal.x, ptLocal.y, this, nullptr );
}
*pResult = 0;
}
So to recap, at the moment the user must physically left click on a item in the tree to select it. Then they can right-click and it will offer to delete this person. But if they go ahead and just right-click on anyone, it will not offer to delete THAT person.
You can get the actual tree item at any given point using the HitTest() member of the CTreeCtrl class. Exactly how and where you do this will depend on your code design but, if you have a pointer to your CTreeCtrl (pwTree in the code below), then you can do something like this:
CPoint ptLocal;
GetCursorPos(&ptLocal);
pWTree->ScreenToClient(&ptLocal); // May not be required in certain handlers?
HTREEITEM hItem = pWTree->HitTest(ptLocal); // Remember to check for NULL return!
You can then either use the returned hItem directly, or use it to explicitly set the tree's selection (to that item), before doing any further processing.
The MS doc: https://learn.microsoft.com/en-us/cpp/mfc/reference/cwnd-class?view=vs-2019 doesn't have a "click" handler, it has CWnd::OnRButtonDblClk, CWnd::OnRButtonDown and CWnd::OnRButtonUp. Which one are you handling?
The reason for your defect might be that you don't let the tree control handle that right button event (to select that new item).
I would suggest to use CWnd::OnContextMenu instead.
One more question. If I create a image-popup dialog, I find it only works when the frontimage (the top one in the image list). If other image is selected, the program will report "the image used in the expression does not exist". I can not understand the logic behind this error.
The following is a modified code pasted in the answer of the previous question. It can work well if the first image is selected, but the error message appears if the second image is selected.
I use GSM 2.30.xxxx
Class CMyDLG : UIframe
{
TagGroup DLG,DLGItems,imgPop
object Init(object self)
{
DLG = DLGCreateDialog("Test",DLGItems)
imgPop = DLGCreateImagePopup()
DLGItems.DLGAddElement( imgPop )
return self.super.init(DLG)
}
image GetSelectedImage( object self )
{
string selectedImageLabel
imgPop.DLGGetValue(selectedImageLabel) //DLGGetValue can return the label of the image diretly
Result("\n" + selectedImageLabel)
// From the string, get the label
//string label = selectedImageLabel.left( selectedImageLabel.find(":") )
//Result("\n" + label)
// From label, return image
//return FindImageByLabel(label)
return FindImageByLabel(selectedImageLabel)
}
}
// main
{
object dlg = Alloc(CMyDLG).Init()
dlg.Pose()
image selected = dlg.GetSelectedImage()
if ( selected.ImageIsValid() )
{
selected.SetName( "Selected" + random())
selected.ShowImage()
}
else Throw( "Error, nothing selected." )
}
Using the test code on GMS 3.3 it works except for the bug mentioned. I presume it's the same for GMS 2.3 but I haven't verified.
To make sure we test the same thing, here are exact instructions and a break-down:
Start out with two images A and B and A being front-most.
Run script
Don't change anything in the dialog
Press OK
ERROR
The dialog - taggroup does not (yet) hold any value. It possibly should, I consider this a bug.
Start out with two images A and B and A being front-most.
Run script
Click the selection box and select "A" from the drop-down
Press OK
A is correctly selected
Start out with two images A and B and A being front-most.
Run script
Click the selection box and select "B" from the drop-down
Press OK
ERROR
The dialog - taggroup does not (yet) hold any value. It definitly should, I consider this a bug. It is most likely what you were describing?
Start out with two images A and B and A being front-most.
Run script
Click the selection box and select "A" from the drop-down
Click the selection box and select "B" from the drop-down
Press OK
B is correctly selected
To summarize:
Yes, there is a bug and nothing wrong with your script.
The selection box only works after selecting an items for the second time.
The example code (first script) in this answer seems to work on any of the open images when selected.
However, there is the (mentioned) bug that it does not work on first selection, only when you select one image and then another.
If your code fails, please provided a slimmed-down code-example of the failing code so that a mistake can possibly be spotted.
I tried adding menu items to an existing menu in Maya using MEL
Example:
menuItem -label "Mylabel" -insertAfter "someEntry" -parent $gMainFileMenu;
menuItem -label "Mylabel2" -insertAfter "someEntry" -parent $gMainFileMenu;
The issue is that the menu doesn't get populated by the normal entries like it should but only by the entries I've added with these 2 lines of code.
For example, the file menu normally contains "New scene", "Open scene", "Save scene", etc., but when I do these 2 lines, it only contains "Mylabel" and "Mylabel2".
Is there a fix or a workaround to make sure the menu gets fully populated? Is there a way to force Maya to build it without the user actually clicking on it?
Actually the problem is that Maya first populates the menu the first time the user opens it. It checks if the menu length and if it's greater than 0, it doesn't populate it. Since you've added 2 entries, the menu length is greater than 0 and doesn't get populated with the standard entries.
To fix that problem there is two ways to do it. You can do it by force-building the menu items or by registering your build menuItem. Bot ways are usable in different cases.
By force building the menu :
What you have to do, is find the function that Maya call to build the menu you need. You can find these functions in the folder <MayaInstall>/scripts/startup/*. A good way to find the procedure name is to open the Maya Console, enable "echo all commands" and then to click the menu you want to see the function called to build it.
In your case the function is called buildFileMenu() and can be found in the script FileMenu.mel.
Now that you have that function name, you have to check it's parameters. Sometime it need a menu name as parameters some times not. See the bottom section about how to find a menu name if you need one.
Now let's build it.
global string $gMainFileMenu; // Retrieve the menu name
buildFileMenu(); // Build it
// Now build your menu
menuItem -divider true -parent $gMainFileMenu SuperMenuDivider;
menuItem -label "MyLabel1" -parent $gMainFileMenu SuperMenuLab1;
menuItem -label "MyLabel2" -parent $gMainFileMenu SuperMenuLab2;
// Create a proc to remove your menu items
global proc RemoveMyMenuItems()
{
if(`menuItem -ex SuperMenuDivider`) deleteUI -mi SuperMenuDivider;
if(`menuItem -ex SuperMenuLab1`) deleteUI -mi SuperMenuLab1;
if(`menuItem -ex SuperMenuLab2`) deleteUI -mi SuperMenuLab2;
}
// You can then call that function when in the unload function of your plugin.
By registering the build menuItem call
What you have to do is use a function called addMenuItemSafe which takes 3 parameters, the menu you want to populate, the name of the function which populates the menu, and a global variable name to hold the callback.
So first thing you have to do is create the function that will populate your menu, then the function to remove it and then call the AddMenuItemSafe method. Note that in the function you have to check if your menu is already created because Maya will call that function every time the menu is shown.
First, the two functions to add and remove our menu entries:
global proc string AddMyMenuItems()
{
// Global variable to hold the test to see if the menu is populated.
global int $gMyMenuItemsTest;
// Menu var needed in our case because we are inserting in the middle of the menu
global string $gMainFileMenu;
if( $gMyMenuItemsTest == 0 )
{
// Actually build your menu.
// Note that if you don't need to insert it after a specific entry,
// You can just do `menuItem -label "blep"`. No need of -ia and -p
// Also, for inserting in the middle you have to put stuff in reverse order.
// If you are just appending, put it in the normal order.
menuItem -label "Mylabel2" -insertAfter "someEntry" -parent $gMainFileMenu MyMenuLab2;
menuItem -label "Mylabel" -insertAfter "someEntry" -parent $gMainFileMenu MyMenuLab;
menuItem -divider true -parent $gMainFileMenu MyMenuDiv;
$gMyMenuItemsTest = 1;
}
return "RemoveMyMenuItems()"; // Returns the callback
}
global proc RemoveMyMenuItems()
{
global int $gMyMenuItemsTest;
if( $gMyMenuItemsTest == 1 )
{
// Delete your items if they exist (yes we are kind of
// doing the check twice, but I find it safe.
// The user could have deleted it from Maya in the command
// line for whatever reason, more robustness is always good.
if(`menu -ex MyMenuDiv`) deleteUI -mi MyMenuDiv;
if(`menu -ex MyMenuLab`) deleteUI -mi MyMenuLab;
if(`menu -ex MyMenuLab2`) deleteUI -mi MyMenuLab2;
}
}
And then the actual call to the AddMenuItemSafe:
// The menu we want to use ... here it is the File Menu.
global string $gMainFileMenu;
// Our variables needed for the addSafe call
global int $gMyMenuItemsTest;
global string $gMyMenuVariable;
$gMyMenuItemsTest = 0;
$gMyMenuVariable = "";
// The menu creation
addMenuItemSafe($gMainFileMenu, "AddMyMenuItems", "gMyMenuVariable");
You are free to put that function call in a plugin instantiation or where ever it pleases you.
For more information about the function AddMenuItemSafe, you can go in your Maya Scripts Directory it should be there "AddMenuItemSafe.mel".
How to find a menu name
Note for the menu variable names, you can "build" them by using the following convention
"g" + View + Name + "Menu"
Where :
View is the view where you can find that menu. Ex: Main, Polygons, Animations, etc.
Name is the name of the menu. Ex: File, Edit, Mesh, etc.
Note Autodesk will sometimes rename menus and use old globalVariable names so using this method might not always work
In MFC how to remove a menu-item of POPUP type. RemoveMenu() either take ID or position. Since, there is no ID for POPUP menu, option left is by using position.
But I am not getting how and where I can call RemoveMenu().
File Edit Test
Test_submenu_1
Test_submenu_2
Test_submenu_3 > submenu_3_item_1
Test_submenu_4
Test_submenu_5
I want to remove Test_submenu_3? I am not getting how do find CMenu object for Test so that I can call RemoveMenu() by passing position "2" for submenu_3_item_1.
Any suggestion for doing this will be greatly appreciated.
Thanks!
You cannot use LoadMenu, since this function does just that.
After modifying loaded menu it is killed when menu object used to load it goes out of scope. You have to modify menu that is currently used.
Your menu is a popup part of the main menu, second in position. It contains 5 items and second one is another popup. To my understanding, you want to remove this item and popup of this item.
To make it work you will have to ask main window for the current menu:
CMenu* pMenu = GetMenu(); // get the main menu
CMenu* pPopupMenu = pMenu->GetSubMenu(2);//(Test menu with item....)
pPopupMenu->RemoveMenu(2, MF_BYPOSITION);
Of course, this code is from the main frame. If you want to use it elsewhere, you will have to access all using pointer to the main frame.
Try the below. You get the Test sub-menu first (index 2), then once you have that you tell it to remove its Test_submenu_3 submenu by position (also 2).
CMenu topMenu;
topMenu.LoadMenu(IDR_YOUR_MENU);
CMenu& testSubMenu = *topMenu.GetSubMenu(2);
testSubMenu.RemoveMenu(2,MF_BYPOSITION);
'Test' is the 3rd menu item (by position) on the top level menu. It's just been rendered horizontally rather than vertically. Assuming you have a handle to the top level menu use the same code you'd use to the get the sub menus as you would to get the 'Test' menu.
You can use this below code to remove the submenu, by comparing name
bool RemoveSubmenu(CMenu * pMenu) {
for (int pos = 0; pos < pMenu->GetMenuItemCount(); pos++) {
wchar_t *name = new wchar_t[mf.cch + 1];
MENUITEMINFO mf;
ZeroMemory(&mf, sizeof(mf));
mf.cbSize = sizeof(mf);
mf.fMask = MIIM_SUBMENU | MIIM_FTYPE | MIIM_STRING;
mf.fType = MIIM_STRING;
mf.dwTypeData = NULL;
if (!GetMenuItemInfo(pMenu->m_hMenu, pos, TRUE, &mf))
break;
if (mf.hSubMenu != NULL){
mf.fMask = MIIM_TYPE;
mf.fType = MFT_STRING;
++mf.cch;
mf.dwTypeData = (LPSTR)name;
if (!GetMenuItemInfo(pMenu->m_hMenu, pos, TRUE, &mf)){
bRet = false;
break;
}
//
// compare sub menu name (i.e mf.dwTypeData) here, do the required
// modifications
//
pMenu->RemoveMenu(pos, MF_BYPOSITION);
bRet = true;
break;
}
}
}