I was wondering how I can set a default path for a Open File dialog in X++.
The situation is this: In Microsoft Dynamics AX there's the form InventTable, which shows all data concerning our inventory.
On of the properties of every item is an image. These images are all stored in the same folder on our server. So when we press the button to set or change the image, I would like the dialog box to automatically go to this folder, so users don't have to go there themselves.
This is the code behind the Change Image-button so far:
void clicked()
{
FilenameFilter filter = ['Image Files','*.bmp;*.jpg;*.gif;*.jpeg'];
BinData binData = new BinData();
str extention, path, nameOfFile;
super();
imageFilePathName = WinAPI::getOpenFileName(element.hWnd(),filter, '', "#SYS53008", '','');
if (imageFilePathname && WinAPI::fileExists(imageFilePathName))
{
[path, nameOfFile, extention] = fileNameSplit(imageFilePathName);
if (extention == '.bmp' ||
extention == '.jpg' ||
extention == '.gif' ||
extention == '.jpeg')
{
binData.loadFile(imageFilePathName);
imageContainer = binData.getData();
inventTableImage.ADUImage = imageFilePathName;
element.saveImage();
element.showLogo();
}
else
{
throw error("#SYS89176");
}
}
}
I've read that I could set a default path in the getOpenFileName-method, but that doesn't seem to work.
The form itself has a method called filenameLookupInitialPath which returns just an empty string.
the default path parameter works fine for me in Ax 2012 RTM 3. Are you sure the code is being executed on the correct tier and the path is correct?
WinAPI::getOpenFileName(0, conNull(), #'C:\users\', '');
Related
I've just added an Item-Filter-Feature to a CComboBox derived class called
ComboBoxFbp in an old MFC application.
BOOL CComboBoxFbp::OnEditChange()
{
CString csText;
if (m_wFbpMode & _FbpMode_UserTextFiltersList) {
GetWindowText(csText);
// This makes the DropDown "flicker"
// ShowDropDown(false);
// Just insert items that match
FilterItems(csText);
// Open DropDown (does nothing if already open)
ShowDropDown(true);
}
return FALSE; // Notification weiterleiten
}
void CComboBoxFbp::FilterItems(CString csFilterText)
{
CString csCurText;
int nCurItem;
DWORD wCurCursor;
// Text/selection/cursos restore
GetWindowText(csCurText);
nCurItem = GetCurSel();
if (nCurItem != CB_ERR && nCurItem >= 0 && nCurItem < GetCount()) {
CString csCurItemText;
GetLBText(nCurItem, csCurItemText);
if (csCurItemText == csCurText) csCurText = csCurItemText;
else nCurItem = CB_ERR;
} else {
nCurItem = CB_ERR;
}
wCurCursor = GetEditSel();
// Delete all items
ResetContent();
csFilterText.MakeLower();
// Add just the items (from the vector of all possibles) that fit
for (auto item : m_vItems)
{
CString csItemText = item.first;
csItemText.MakeLower();
if (!csFilterText.IsEmpty() && csItemText.Find(csFilterText) < 0)
continue;
const int i = AddString(item.first);
SetItemData(i, item.second);
}
// Text/selection/cursos restore
if (nCurItem != CB_ERR) SelectString(-1, csCurText);
else SetWindowText(csCurText);
SetEditSel(LOWORD(wCurCursor), HIWORD(wCurCursor));
}
So when the user types, the long list of items in the DropDown gets filtered accordingly. Everything's fine so far.
The size/height of the ListBox/DropDown doesn't change once its open. It does change accordingly when die DropDown opens. Meaning if there are only 2 items the DropDown is only 2 items high.
My issue
When the user enters a text where just one item fits the DropDown is only 1 item in height (this happens with some user workflows, i.e. user manually closes & opens the DropDown).
Now when the user now changes the text so multiple items are fitting the height stays 1 item and it looks weird as even the scrollbar doesn't look correct as it doesn't fit.
What I've tried so far
I cannot use CComboBox::SetMinVisibleItems (or the MSG behind it) as it only works in a Unicode CharacterSet (which I'm not able to change in this old application) and from WinVista onwards (app runs on WinXP).
The only other option is to close and open the DropDown so it gets redrawn correctly with the correct height (see // This makes the DropDown "flicker" in Source Code above).
Now going with option 2 I don't want the user to see the closing and opening ("flicker") of the DropDown after every key he is pressing.
To prevent this I've tried a couple of solutions I've found but none works in my case with a ComboBox-DropDown. Here's a list of methods I've put just before the ShowDropDown(false) and just after the ShowDropDown(true).
EnableWindow(false/true);
(Un)LockWindowUpdate();
SendMessage(WM_SETREDRAW, FALSE/TRUE, 0)
With all three calls I still see the DropDown closing/opening.
Do you guys have other ideas how I can prevent this flicker?
Thanks in advance
Soko
This is an XY question.
It should be easier to use the following approach to adjust the height of the ComboBox
Use GetComboBoxInfo to get the handle of the list control.
Use OnChildNotify or ON_CONTROL_REFLECT and capture CBN_DROPDOWN.
In the handler of the message resize the window as needed Use SetWindowPos and just change the size.
I have a saving event handler in Sitecore where when the date of an item changes (from say 8/7/2014 to 9/7/2014) I want it to be moved into another folder.
It currently somewhat does that, but only if I change the date twice.
If I go from 8/7/2014 > 9/6/2014 it does nothing. If I then change the same item's date to 9/7/2014 it moves it into the correct folder.
If I debug the code it follows the exact same path both times .. anyone have a reason as to why this might be happening?
One of the other problems was that the saving handler was running twice, once with the new date value, once with the old. Changing to the saved handler worked much better because I only have access to the newly saved values, not the old and new values.
Since there were a few comments asking for it, here's the working code to move and item from folder parent to parent > year > month. Keep in mind this is using glassmapper so it would be slightly different if not using glass mapper.
protected void MoveItemToMonthDateFolder<T>(T scItem, Func<T, DateTime> dateSelector, Models.Item parentFolder) where T : Models.Item
{
var date = dateSelector(scItem);
if (!parentFolder.Children.Any(x => x.Name.Contains(date.Year.ToString())))
{
var yearFolder = sitecoreService.Create(parentFolder, new Folder { Name = date.Year.ToString() });
var monthFolder = sitecoreService.Create(yearFolder, new Folder { Name = date.Month.ToString() });
sitecoreService.Move(scItem, monthFolder);
}
else
{
var yearFolder = parentFolder.Children.First(x => x.Name.Equals(date.Year.ToString(), StringComparison.CurrentCultureIgnoreCase));
var monthFolder = yearFolder.Children.FirstOrDefault(x => x.Name.Equals(date.Month.ToString(), StringComparison.CurrentCultureIgnoreCase));
if (monthFolder == null)
{
monthFolder = sitecoreService.Create(yearFolder, new Folder { Name = date.Month.ToString() });
}
sitecoreService.Move(scItem, monthFolder);
}
}
Brief description: IN my Qt utility , I want that as soon as user hit the close button following things happens
1) A File Dialog box appear with save /cancel options and with
default file name in it.
2) If the user has saved the file in different location on his
computer , I should be able to write logs on that saved file.
I have done the first part but I am clueless on how to retrieve the file name with full path when the user has already closed the dialog box.
MY code for part 1 is given below.
void some_class ::on_write_file()
{
// some code ..
bla bla bla
switch( set_file_name_for_logging( QString::fromStdString( filename ) , this ) )
{
case QDialog::Accepted :
std::cout <<" Retrive filename and full path name from the location where user has saved the file " and write on it;
break;
case QDialog::Rejected :
break;
default :
throw_error( "Unexpected return value from save_ dump file dialog" );
break;
}
}
}
int set_file_name_for_logging( const QString& str, som_class *cal )
{
QFileDialog file_dialog( cal );
file_dialog.setDirectory(".");
file_dialog.setAcceptMode(QFileDialog::AcceptSave);
file_dialog.setNameFilter( ("Text files (*.txt )") );
file_dialog.selectFile( str );
int ret = file_dialog.exec();
return ret ;
}
You can access the chosen file using file_dialog.selectedFiles(). Also take a look at static function QFileDialog::getSaveFileName.
As written in my comment, you can use the following method:
QStringList QFileDialog::selectedFiles() const
Returns a list of strings containing the absolute paths of the selected files in the dialog. If no files are selected, or the mode is not ExistingFiles or ExistingFile, selectedFiles() contains the current path in the viewport.
Also, note that your code will need some refactoring to actually pass either the QFileDialog over or the path string itself. Currently, there is no direct access to them.
If you chooes to pass the QFileDialog somehow, you can get the string list, and it will only contain one item in your case, so you could use the first() convenience method then.
I'm sending a bundle of cards to Glass with the Mirror API (c# library)
I know that you can use the default delete menu item on single cards, but is there a way to provide delete functionality for an entire bundle, ideally the result of one action by the users?
I have successfully used the DELETE action on a menu item with the code below
MenuItem mi = new MenuItem();
mi.Action = "DELETE";
TimelineItem tli = new TimelineItem()
{
Html = itemHtml.ToString(),
Notification = new NotificationConfig() { Level = "DEFAULT" },
MenuItems = new List<MenuItem>() { mi }
};
Is there a way to add this delete menu item to a bundle cover? I know this may be tricky because clicking the bundle cover causes you to navigate into the child cards thus no menu is present like on single cards. I'm looking for something (which I did try but it just ignored the menu item) like this:
MenuItem mi = new MenuItem();
mi.Action = "DELETE";
TimelineItem tli = new TimelineItem()
{
Html = itemHtml.ToString(),
Notification = new NotificationConfig() { Level = "DEFAULT" },
IsBundleCover = true,
BundleId = bundleId,
MenuItems = new List<MenuItem>() { mi }
};
If not possible on a cover card, is there a way to do this for a bundle by adding delete menu items to the child cards?
Any suggestions would be appreciated
You can use customized menu to do this. The code below is using Java but C# should be similar:
Add customized menu item to the card:
List<MenuValue> menuValueList = new ArrayList<MenuValue>();
menuValueList.add(new MenuValue().setIconUrl(iconUrl).setDisplayName("Delete All"));
MenuItem menuItem = new MenuItem();
menuItem.setValues(menuValueList).setId("delete_bundle_A").setAction("CUSTOM");
List<MenuItem> menuItemList = new ArrayList<MenuItem>();
menuItemList.add(menuItem);
timelineItem.setMenuItems(menuItemList);
Define the controller which handles the callback request of Mirror server notification:
if (notification.getCollection().equals("timeline") && notification.getUserActions().contains(new UserAction().setType("CUSTOM").setPayload("delete_bundle_A"))) {
deleteCards(credential, bundleId);
}
The delete card function:
// if bundleId is null or "", delete all cards
public static void deleteCards(Credential credential, String bundleId) throws IOException {
if (bundleId == null) {
bundleId = "";
}
Mirror.Timeline timelineItems = MirrorClient.getMirror(credential).timeline();
Mirror.Timeline.List list = timelineItems.list();
List<TimelineItem> timelineItemList = null;
do {
TimelineListResponse response = list.execute();
timelineItemList = response.getItems();
if (timelineItemList != null && timelineItemList.size() > 0) {
for (TimelineItem item : timelineItemList) {
if (bundleId == "" || bundleId.equalsIgnoreCase(item.getBundleId())) {
LOG.info("Deleting card " + item.getId());
MirrorClient.deleteTimelineItem(credential, item.getId());
}
}
list.setPageToken(response.getNextPageToken());
} else {
break;
}
} while (list.getPageToken() != null && list.getPageToken().length() > 0);
}
Finally, don't forget to subscribe timeline notification when application starts up:
String notifyUrl = "https://mirrornotifications.appspot.com/forward?url=" + "http://yourServer.com/notify";
Subscription subscription = MirrorClient.insertSubscription(credential, notifyUrl, userId, "timeline");
It isn't clear if you're asking how to create the menu items to delete the entire bundle at once, or if you're looking for code to do the actual delete.
Yuan provides some very good answers to both (not least of which because he actually provides code, which I won't), but there are three things you might also want to consider.
1) You can't have a menu on the bundle cover, but if you don't explicitly specify a bundle cover, then the most recent card will be shown as the cover and will also be shown as the first card in the bundle. You'd be able to get to the menu this way. (The default messaging app works this way, for example, but the first card has the same menu as the rest.)
2) You don't need to create a new menu item. You can leverage the DELETE menu item, if you wish. You'll get a delete notification for one of the cards in the bundle and you can then read the bundleId and delete the rest.
3) You don't need to loop through all the cards you've inserted just to find ones that have that bundleId. That is horribly inefficient. I am not fluent in C#, but from reading the documentation at https://developers.google.com/resources/api-libraries/documentation/mirror/v1/csharp/latest/classGoogle_1_1Apis_1_1Mirror_1_1v1_1_1TimelineResource_1_1ListRequest.html, I get the sense that you can create a ListRequest and then set the bundleId before executing the query and get the results.
So I think you can change Yuan's code to something like:
Mirror.Timeline.List list = timelineItems.list();
list.BundleId = bundleId;
List<TimelineItem> timelineItemList = null;
do {
TimelineListResponse response = list.execute();
timelineItemList = response.getItems();
if (timelineItemList != null && timelineItemList.size() > 0) {
for (TimelineItem item : timelineItemList) {
LOG.info("Deleting card " + item.getId());
MirrorClient.deleteTimelineItem(credential, item.getId());
}
list.setPageToken(response.getNextPageToken());
} else {
break;
}
} while (list.getPageToken() != null && list.getPageToken().length() > 0);
(this should be treated as pseudo-code, at best)
If you're confident how many items you've put into a bundle, you might also be able to just set list.MaxResults and not have to iterate over the pages of results. So perhaps something more like
Mirror.Timeline.List list = timelineItems.list();
list.BundleId = bundleId;
list.MaxResults = 20; // Set to more than the max number of items in a bundle
TimelineListResponse response = list.execute();
List<TimelineItem> timelineItemList = response.getItems();
if (timelineItemList != null && timelineItemList.size() > 0) {
for (TimelineItem item : timelineItemList) {
LOG.info("Deleting card " + item.getId());
MirrorClient.deleteTimelineItem(credential, item.getId());
}
}
There doesn't appear to be a way to delete a bundle in one step but it's still possible...
You can do a GET on /Timeline to get a list of items your app has pushed to the users timeline. Filter that out to find the entries with the bundleId you want to delete. For each of those items, call DELETE /Timeline/{itemid}
I have a menu button inside a CMFCToolbar and I would like to replace the bitmap of the button each time a different entry is selected in the menu as each entry has its own icon.
I succeed in changing the icon using CMFCToolBarMenuButton::SetImage but it changes the icon in the menu entry too. Too bad.
alt text http://www.freeimagehosting.net/uploads/137269b0f2.jpg alt text http://www.freeimagehosting.net/uploads/879d03843a.jpg
Here is a sample of code:
if ( (pToolbar != NULL) && (idBase != 0) )
{
int ixButtonToReplace = pToolbar->CommandToIndex(idBase);
CMFCToolBarMenuButton* pBtnToReplace = dynamic_cast<CMFCToolBarMenuButton*>
(pToolbar->GetButton(ixButtonToReplace));
if ( pBtnToReplace )
{
const CObList& listCommands = pBtnToReplace->GetCommands();
POSITION pos = listCommands.GetHeadPosition();
while ( pos != NULL )
{
CMFCToolBarMenuButton* pItem = (CMFCToolBarMenuButton*) listCommands.GetNext(pos);
if ( pItem && (pItem->m_nID == idButtonToReplaceWith) )
{
pBtnToReplace->SetImage(pItem->GetImage());
}
}
}
}
Any ideas? Thank you.
It works out-of-box. The only you need is to call CMFCToolBar::AddToolBarForImageCollection so MFC could know which images to use.
Not sure what you mean by the menu button is changed too?
If another button is changed with the single setImage call to the obvious indication is they share a resource ID of some sort, the only solution would be to ensure they have different ID's (which would require making sure both resources are handled separately).
But it's a long time since I messed in MFC resource files to confirm this.