It was mentioned in some of my other threads on my app on that my code was incorrect because the apply button is present. I understand that now. It was said to collect the controls and then when apply is hit to send the data.
I have now idea how to approach that. So for the sake of general education. I have 1 property sheet and 5 property pages. For the sake of just general controls in use. Between all the 5, there are only radio controls and buttons, no edit controls (yet).
Let's assume there are 2 radios buttons and 1 button on each page.. where page 1 is radio1, radio2, button 1 and page 2 is radio3, radio4, button2....and so on.
I know that when the user selects something like a button or radio that the IsModified(TRUE) needs to be called to enable the apply button from grayed to active.
What would the code look like to scan all the controls and then apply them? I've never done it and I can't seem to find an example that isn't already super busy to gain the understanding of how to do it.
Anyone have a tutorial or code snippet or build a primer from the controls that I described above that could demonstrate how to execute this?
Update:
Ok so I have the DDX variables added:
void CSettingsUserTabs::DoDataExchange(CDataExchange* pDX)
{
CMFCPropertyPage::DoDataExchange(pDX);
DDX_Control(pDX, STYLE_3D_USER, m_style_3d);
DDX_Control(pDX, STYLE_FLAT_USER, m_style_flat);
DDX_Control(pDX, STYLE_FLAT_SHARED_HORZ_SCROLL_USER, m_style_flat_shared_h_scroll);
DDX_Control(pDX, STYLE_3D_SCROLLED_USER, m_style_3d_scroll);
DDX_Control(pDX, STYLE_3D_ONENOTE_USER, m_style_onenote);
DDX_Control(pDX, STYLE_3D_VS2005_USER, m_style_vs2005);
DDX_Control(pDX, STYLE_3D_ROUNDED_USER, m_style_3d_rounded);
DDX_Control(pDX, STYLE_3D_ROUNDED_SCROLL_USER, m_style_3d_rounded_scroll);
}
My radio selection look like:
void CSettingsUserTabs::OnBnClicked3dUser()
{
//AfxGetMainWnd()->SendMessage(WM_COMMAND, STYLE_3D_USER);
UpdateData(TRUE);
}
void CSettingsUserTabs::OnBnClickedFlatUser()
{
// TODO: Add your control notification handler code here
//AfxGetMainWnd()->SendMessage(WM_COMMAND, STYLE_FLAT_USER);
UpdateData(TRUE);
}
..... and the rest of them....
So to me, when I click any radio button, I expect that it scans all of controls on that property page? If so, all the variables have the values..do I call IsModified(); to enable the apply button...which then executes all the radio values i.e. only one selected? Is that the flow?
Update 2:
So this is what my modal dialog code is in MainFrame.cpp:
void CMainFrame::OnSettingsTools()
{
SettingsSheet SettingsSheet(L"Application Settings");
CSettingsPowerUser pgePowerUser;
CSettingsToolbars pgeToolbars;
CSettingsTheme pgeTheme;
CSettingsUserTabs pgeUserTabs;
CSettingsReset pgeReset;
SettingsSheet.AddPage(&pgeToolbars);
SettingsSheet.AddPage(&pgeTheme);
SettingsSheet.AddPage(&pgeUserTabs);
SettingsSheet.AddPage(&pgePowerUser);
SettingsSheet.AddPage(&pgeReset);
INT_PTR nRet = -1;
nRet = SettingsSheet.DoModal();
// Handle the return value from DoModal
switch (nRet)
{
case -1:
AfxMessageBox(_T("Dialog box could not be created!"));
break;
case IDABORT:
// Do something
AfxMessageBox(_T("ABORT!"));
break;
case IDOK:
// Do something
OnUserTabStyles(1);
AfxMessageBox(_T("OK!"));
break;
case IDCANCEL:
// Do something
AfxMessageBox(_T("CANCEL"));
break;
default:
// Do something
break;
};
}
The routine to verify if any of the radios buttons are changed in SettingsTabs.cpp:
void CSettingsUserTabs::OnTabRadioClicked(UINT nCmdID)
{
BOOL IsChecked = nCmdID;
CheckRadioButton(STYLE_3D_USER, STYLE_3D_ROUNDED_SCROLL_USER, nCmdID);
UpdateData(TRUE);
m_tabCmdID = nCmdID;
SetModified();
}
What the member variables look like in SettingsUserTabs.cpp:
void CSettingsUserTabs::DoDataExchange(CDataExchange* pDX)
{
CMFCPropertyPage::DoDataExchange(pDX);
DDX_Radio(pDX, STYLE_3D_USER, m_style_3d);
DDX_Radio(pDX, STYLE_FLAT_USER, m_style_flat);
DDX_Radio(pDX, STYLE_FLAT_SHARED_HORZ_SCROLL_USER, m_style_flat_h_scroll);
DDX_Radio(pDX, STYLE_3D_SCROLLED_USER, m_style_3d_scroll);
DDX_Radio(pDX, STYLE_3D_ONENOTE_USER, m_style_3d_onenote);
DDX_Radio(pDX, STYLE_3D_VS2005_USER, m_style_vs2005);
DDX_Radio(pDX, STYLE_3D_ROUNDED_USER, m_style_3d_rounded);
DDX_Radio(pDX, STYLE_3D_ROUNDED_SCROLL_USER, m_style_3d_rounded_scroll);
}
What the constructor looks like in SettingsUserTabs.cpp:
CSettingsUserTabs::CSettingsUserTabs()
: CMFCPropertyPage(IDD_SETTINGS_TABS)
, m_style_3d(FALSE)
, m_style_flat(FALSE)
, m_style_flat_h_scroll(FALSE)
, m_style_3d_scroll(FALSE)
, m_style_3d_onenote(FALSE)
, m_style_vs2005(FALSE)
, m_style_3d_rounded(FALSE)
, m_style_3d_rounded_scroll(FALSE)
, m_tabCmdID(FALSE)
{
}
This issue I'm seeing now is when I try to use the member variable m_tabCmdID it is coming back to unknown identifier so I'm not sure why the member variable isn't be seen. I am was expecting to use it like OnUserTabStyles(m_tabCmdID); so that it would pass the argument of the selected button to the method OnUserTabStyles. For now I just dumped a 1 in there to see if the mechanism works. I just am not clear how to access the member variable from the SettingsUserTabs.cpp from the IDOK. What am I missing?
EDIT: The range of options are sequential in the resource.h as 200-207, that is something I'm aware of and I know many don't like range options as they can get corrupted...this is my code, so I have no worries about the range being messed with.
Update 3:
Ok, so I finally understand the mechanism that Constantine described with the help of:
https://helgeklein.com/blog/2009/10/radio-buttons-in-mfc-visual-studio-2008-c/
I didn't have the tab order right nor did I have the first control set to true for the group.
With that, I now get the values 0-7 mentioned in the button group when I debug as I click each radio button based on its position in the group from 0-7 i.e. 8 buttons. Here is what the code looks like now.
SettingsUserTabs.cpp:
CSettingsUserTabs::CSettingsUserTabs()
: CMFCPropertyPage(IDD_SETTINGS_TABS)
, m_style_tabs(FALSE)
{
}
void CSettingsUserTabs::DoDataExchange(CDataExchange* pDX)
{
CMFCPropertyPage::DoDataExchange(pDX);
DDX_Radio(pDX, STYLE_3D_USER, m_style_tabs);
}
void CSettingsUserTabs::OnTabRadioClicked(UINT nCmdID)
{
UpdateData(TRUE);
BOOL RadioValueSelected = m_style_tabs; // only here to see 0-7 value for debugging only, not needed, test only
SetModified();
}
The mainframe.cpp above (Update 2): void CMainFrame::OnSettingsTools() is still the same.
So now here is where my question still isn't clear, I call the domodal from mainframe.cpp, the member variable m_style_tabs is in SettingsUserTabs.cpp. When I try to access the member variable, it says unknown identifier when I try to do something like this after the domodal int temp = m_styles_tabs;. I have a this in the mainframe.cpp
void CMainFrame::DoDataExchange(CDataExchange* pDX)
{
// TODO: Add your specialized code here and/or call the base class
CMDIFrameWndEx::DoDataExchange(pDX);
}
I would expect that the member would be seen in mainframe.cpp so I can process it after the domodal which is what I thought the whole point of this is?
How do I access the member variable so I can do the OnApply to it? I think I almost got it, I am just unclear on how to execute the last few steps for the actual apply itself.
Update 4:
The reason I ask about the restart is when the user selects the tabs property page and choose 1 of 8 choices, that option is stored in the registry and read during the Oncreate and then uses that style. Since it is OnCreate I haven't found a way to "redraw" or use the new tab setting except to restart the app. So by doing the reg save > respawn > end old ...I would want the dialog to reopen at the same tab property page so the user can see what the changes are if the apply was selected...vs. some dialog that says "restart"..or whatever. Below is the code used to demonstrate how it is working now.
OutputWnd.cpp
int COutputWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDockablePane::OnCreate(lpCreateStruct) == -1)
return -1;
CRect rectDummy;
rectDummy.SetRectEmpty();
// Create User Define tab style:
int UserTabStyle = AfxGetApp()->GetProfileInt(_T("Settings"), _T("UserTabStyle"), 0); //Get value from registry
// If the key doesn't exist, UserTableStyle will be 0 or FALSE;
if (UserTabStyle != FALSE && UserTabStyle <= 8) { // User selected tab style type
int EnumUserTabStyle = UserTabStyle - 1; // Fix enum if key doesn't exist.
if (!m_wndTabs.Create(static_cast<CMFCTabCtrl::Style>(EnumUserTabStyle), rectDummy, this, 1))
{
TRACE0("Failed to create output tab window\n");
return -1; // fail to create
}
}
else { // Default tabs style if Reg key does not exist i.e. new install/program reset
if (!m_wndTabs.Create(CMFCTabCtrl::STYLE_FLAT, rectDummy, this, 1))
{
TRACE0("Failed to create output tab window\n");
return -1; // fail to create
}
}
… rest of function....
Update 5:
Here is the Apply in use from SettingsUserTabs.cpp:
BOOL CSettingsUserTabs::OnApply()
{
// TODO: Add your specialized code here and/or call the base class
AfxGetApp()->WriteProfileInt(_T("Settings"), _T("UserTabStyle"), m_style_tabs); // Save value to registry
return CMFCPropertyPage::OnApply();
}
Update 6:
Everything to this point is working, I ran into a road block trying to apply lessons learned here and at this link:
MFC MDI Substituting a class member dynamically
What was done in this topic was for the OutputWnd pane which works brilliantly! My MDI opens a file and uses CTabView and using the link shown in Update 6 allowed me to change the tabs on boot. Now that I have the OutputWnd doing it with OnApply, I'm trying to apply it to the document view when a file is loaded. I'm running into an access violation when I call the new function I created in TrainView.cpp and calling it from UserSettingsTabs.cpp. I thought it was the static_cast operation, but even if I do a simple bold using GetControlTabs() that also crashes (Shown in the commented out code, was on boot, now in OnApply to test theory). So clearly I need to capture the MDI document but not sure how that is done. I thought it would be as simple as:
GetTabControl().ModifyTabStyle(static_cast<CMFCTabCtrl::Style>(EnumUserTabStyle));
But when that crashed with a Cx000000005 access violation, I knew something was wrong on my end. I can't modify the CTabView operation, so I'm looking to see if we can fix what I'm doing wrong to have the OnApply change the tab styles without restarting as done in the OutputWnd we just fixed.
So as it stands, the OnApply that is working and now modded to try an integrate the CTabView functionality:
SettingsUserTabs.cpp:
BOOL CSettingsUserTabs::OnApply()
{
BOOL bResult = CMFCPropertyPage::OnApply();
if (bResult)
{
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();
CTrainView* TrainTabs; // User Call from anywhere method
TrainTabs->TrainDocUpdateTabsControl();
}
return bResult;
}
I added the function CTrainView::TrainDocUpdateTabsControl() to update the tabs...the rest of the code is fully operation i.e. void CTrainView::OnInitialUpdate()
The TrainView.cpp:
IMPLEMENT_DYNCREATE(CTrainView, CTabView)
void CTrainView::OnInitialUpdate()
{
CMainFrame* pMainFrame = (CMainFrame*)AfxGetMainWnd();
pMainFrame->m_wndOutput.AddStringDebugTab(_T("Debug: TrainView--CTrainView::OnInitialUpdate()."));
// add views // cmb
AddView(RUNTIME_CLASS(CInformationView), AfxStringID(IDS_INFORMATION));
AddView(RUNTIME_CLASS(CChaptersView), AfxStringID(IDS_CHAPTERS));
// Nicely hack to access protected member
class CMFCTabCtrlEx : public CMFCTabCtrl
{
public:
void SetDisableScroll() { m_bScroll = FALSE; }
};
// One-Liner to Disable navigation control
((CMFCTabCtrlEx*)&GetTabControl())->SetDisableScroll();
GetTabControl().EnableTabSwap(TRUE);
GetTabControl().SetLocation(CMFCBaseTabCtrl::Location::LOCATION_BOTTOM);
//GetTabControl().SetActiveTabBoldFont(TRUE);
GetTabControl().EnableAutoColor(TRUE);
// Modify User Define tab style:
int UserTabStyle = AfxGetApp()->GetProfileInt(_T("Settings"), _T("UserTabStyle"), 0); //Get value from registry
// If the key doesn't exist, UserTableStyle will be 0 or FALSE;
if (UserTabStyle != FALSE && UserTabStyle <= 8) { // User selected tab style type
int EnumUserTabStyle = UserTabStyle - 1; // Fix enum if key doesn't exist.
GetTabControl().ModifyTabStyle(static_cast<CMFCTabCtrl::Style>(EnumUserTabStyle));
}
else { // Default tabs style if Reg key does not exist i.e. new install/program reset
GetTabControl().ModifyTabStyle(CMFCTabCtrl::STYLE_FLAT);
}
CTabView::OnInitialUpdate();
}
void CTrainView::TrainDocUpdateTabsControl()
{
CTabView::AssertValid();
GetTabControl().SetActiveTabBoldFont(TRUE); << CAUSES Cx000000005 ACCESS ERROR CRASH WHEN CALLED.
//int EnumUserTabStyle;
//int UserTabStyle = AfxGetApp()->GetProfileInt(_T("Settings"), _T("UserTabStyle"), 0); //Get value from registry
//((CMainFrame*)AfxGetMainWnd())->GetTabControl().ModifyTabStyle(static_cast<CMFCTabCtrl::Style>(EnumUserTabStyle));
}
Update 6 EDIT:
The tabview is created from Application.cpp like this:
//Load Train Template
m_pkDocTrainTemplate = new CMultiDocTemplate(
IDR_TRAIN, // Loads TRAIN operation
RUNTIME_CLASS(CTrainDoc),
RUNTIME_CLASS(CChildFrame),
RUNTIME_CLASS(CTrainView));
AddDocTemplate(m_pkDocTrainTemplate);
I tried to cast the m_pkDocTrainTemplate as I thought that was the pointer? Since it is MDI, I wasn't sure if there was an index issue since there can be multiple docs open simultaneously?
Below is an example of an application I wrote some time ago. It's a simple "Settings" dialog. Unlike yours, this one is derived from CDialogEx. But as CPropertyDialog is derived from CDialog, these apply in your case too.
Using the Wizard, I added member variables to the dialog class, bound to the dialog controls. Choose "Value", rather than "Control" in the "Category" combo in the Wizard. These are declared in the class definition. For simplicity, I only show three. There is a CString, an int and a BOOL variable, bound to an edit, a combo-box (drop-down list) and a check-box control respectively.
class CSettingsDlg : public CDialogEx
{
.
.
public:
CString m_DBConn;
int m_DumpSQL;
BOOL m_bLineNums;
}
In the implementation, the Wizard has modified the constructor and the DoDataExchange() member functions:
CSettingsDlg::CSettingsDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CSettingsDlg::IDD, pParent)
, m_DBConn(_T(""))
, m_DumpSQL(0)
, m_bLineNums(FALSE)
{
}
void CSettingsDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT_DBCONN, m_DBConn);
DDV_MaxChars(pDX, m_DBConn, 255);
DDX_CBIndex(pDX, IDC_COMBO_DUMPSQL, m_DumpSQL);
DDV_MinMaxInt(pDX, m_DumpSQL, 0, 2);
DDX_Check(pDX, IDC_CHECK_LINENUMS, m_bLineNums);
}
The values in the constructor are the initial (default) ones. The DoDataExchange() function calls the DDX/DDV routines. The DDX routines perform the transfer of data (controls<->variables), while the DDV ones the validation - they are optional. The DoDataExchange() function is called by UpdateData(). Also, the default implementation of OnOK() calls UpdateData(TRUE), and if successful closes the dialog.
You will need to enable the Apply button if something has been modified. You can capture notification messages like EN_CHANGE, EN_UPDATE, BN_CLICKED etc (add an event in the property editor) and call the SetModified() function - this can prove quite tedious, but I can't see any other way to do it.
Such a dialog class can be used in the application as shown below:
void CChildView::OnSetoptions()
{
// Create a Settings-dialog class instance
CSettingsDlg sd; // Main application window as parent - will block every UI item in the application
// Set initial values for the member variables
sd.m_DBConn = szDBconn;
sd.m_DumpSQL = nDumpSQL;
sd.m_bLineNums = bDumpLineNums;
if (sd.DoModal() == IDOK)
{
// Store the values entered by the user
lstrcpyn(szDBconn, sd.m_DBConn, MAX_PATH);
nDumpSQL = sd.m_DumpSQL;
bDumpLineNums = sd.m_bLineNums;
}
}
EDIT :
I have a usage example in the code section just above. The procedure is create an instance of the dialog class, set the member variables' values (initial values, eg read from the registry), call DoModal() and if successful store (copy) the variables somewhere else (if not discard them). This should be done in some event handler, like CMainFrame::OnSettingsTools() in Update2. The CMainFrame::DoDataExchange() override makes no sense.
It's quite strange that you can't access the m_style_tabs variable. Isn't it a non-static, public member of the CSettingsUserTabs class? It should be declared in SettingsUserTabs.h. Won't it work if you acceess it as pgeUserTabs.m_style_tabs? In the CSettingsUserTabs class functions it can accessed simply as m_style_tabs. Also I see it is initialized as FALSE in the constructor. Is it a BOOL and not an int? (btw BOOL is defined as int in Win32, so the compiler won't complain) But the Wizard generates an int variable for radio-buttons, optionally with range validation as well.
Another point, you don't normally need to call UpdateData(TRUE) in CSettingsUserTabs::OnTabRadioClicked(). Please leave only the SetModified() call there. UpdateData(TRUE) is typically called in the OnOK() function. And usually you don't need to override these, because the default implementation is sufficient. The CPropertyPage documentation btw mentions that The default implementation of OnApply calls OnOK.
EDIT 2 :
In OnApply() you should first check if validation was successful. Also, OnCreate() isn't a "method" that can be called directly. It should be considered an "event". It's called by the framework when a window is created. You should instead call Create(). In your case you can destroy the Output Window and create it anew (with the new style). But, I see that the CMFCTabCtrl class has a ModifyTabStyle() function, which you can try calling, (without destroying the windows and creating it again). So, your code would become:
BOOL CSettingsUserTabs::OnApply()
{
BOOL bResult = CMFCPropertyPage::OnApply();
if (bResult)
{
AfxGetApp()->WriteProfileInt(_T("Settings"), _T("UserTabStyle"), m_style_tabs); // Save value to registry
((CMainFraime*)AfxGetMainWnd())->m_wndOutput.m_wndTabs.ModifyTabStyle((CMFCTabCtrl::Style)m_style_tabs);
}
return bResult;
}
The above code won't compile, because the m_wndOutput and m_wndTabs members are protected. You will have to make them public.
I am trying to implement a custom Windows Media Foundation media sink for use in a UWP application that will use it via MediaCapture::PrepareLowLagRecordToCustomSinkAsync().
However, I am currently running into an issue where the ProcessSample() function of my IMFStreamSink is never called, even though I queue the event in my IMFMediaSink::OnClockStart().
I have followed the documentation here ( https://learn.microsoft.com/en-us/windows/win32/medfound/media-sinks ) and also taken a look at the "Simple Communication" sample.
Here is the output that I am getting, I have logged every function call and the HRESULT of every call that my sinks execute:
MyCaptureMediaSink::SetPresentationClock
OK: Clock->AddClockStateSink(this)
MyCaptureStreamSink::BeginGetEvent
OK: MediaEventQueue->BeginGetEvent(pCallback, punkState)
MyCaptureMediaSink::OnClockStart
MyCaptureStreamSink::QueueEvent
OK: MediaEventQueue->QueueEventParamVar(met, guidExtendedType, hrStatus, pvValue)
OK: Sink->QueueEvent(MEStreamSinkStarted, GUID_NULL, S_OK, nullptr)
MyCaptureStreamSink::QueueEvent
OK: MediaEventQueue->QueueEventParamVar(met, guidExtendedType, hrStatus, pvValue)
OK: Sink->QueueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, nullptr)
What I think I should be seeing after the last line is a call to MyCaptureStreamSink::EndGetEvent() due to MEStreamSinkStarted being received, followed by another BeginGetEvent() and EndGetEvent() pair and a call ProcessSample() because the next event would be the MEStreamSinkRequestSample event.
Am I missing some functions that I still need to call in order to get a call to these functions by the MediaCapture system?
I have found the solution to my problem:
I re-implemented my stream sink without using winrt::implements, which seems to work as intended. What I assume (I'm still not 100% sure, though) is happening is that since IMFStreamSink derives from IMFMediaGenerator, the winrt::implemnets does not generate the QueryInterface properly for the base type. In my own implementation, I need to explicitly handle it.
This is the old class declaration, which doesn't work:
class MyStreamSink : public winrt::implements<MyStreamSink, IMFStreamSink>
{
// ...
};
and this is the class that works:
class MyStreamSink: public IMFStreamSink
{
virtual HRESULT QueryInterface(const IID& riid, void** ppvObject) override
{
if (riid == __uuidof(IMFMediaEventGenerator))
{
*ppvObject = static_cast<IMFMediaEventGenerator*>(this);
}
//...
}
}
An even better approach would be to override winrt::is_guid_of. By this approach you get one shared v-table and not two separate v-tables. For more information: How do I use C++/WinRT to implement a classic COM interface that derives from another classic COM interface
template<>
inline bool winrt::is_guid_of<IMFStreamSink>(guid const& id) noexcept
{
return winrt::is_guid_of<IMFStreamSink, IMFMediaEventGenerator>(id);
}
class MyStreamSink : public winrt::implements<MyStreamSink, IMFStreamSink>
{
//...
};
I hope this falls within the realm of this forum:
I want to use the windows shell(?) to allow users to select a number of files before allowing my programme to do a few things to them. For this I found the MSDN sample "CommonFileDialogModes" - http://msdn.microsoft.com/en-us/library/windows/desktop/dd940350%28v=vs.85%29.aspx
In the sample under this class:
class CFileOpenBasketPickerCallback : public IFileDialogEvents, public IFileDialogControlEvents
they have this function:
// IFileDialogEvents
IFACEMETHODIMP OnFileOk(IFileDialog *pfd)
{
// if this button is in the "Add" mode then do this, otherwise return S_OK
IFileOpenDialog *pfod;
HRESULT hr = pfd->QueryInterface(IID_PPV_ARGS(&pfod));
if (SUCCEEDED(hr))
{
IShellItemArray *psia;
hr = pfod->GetSelectedItems(&psia);
if (SUCCEEDED(hr))
{
ReportSelectedItems(pfd, psia);
psia->Release();
}
pfod->Release();
}
return S_FALSE; // S_FALSE keeps the dialog up; return S_OK to allow it to dismiss.
}
which calls:
void ReportSelectedItems(IUnknown *punkSite, IShellItemArray *psia)
{
DWORD cItems;
HRESULT hr = psia->GetCount(&cItems);
for (DWORD i = 0; SUCCEEDED(hr) && (i < cItems); i++)
{
IShellItem *psi;
hr = psia->GetItemAt(i, &psi);
if (SUCCEEDED(hr))
{
PWSTR pszName;
hr = GetIDListName(psi, &pszName);
// .. I've cut some of this out for the example
CoTaskMemFree(pszName);
}
psi->Release();
}
}
}
Now I know pszName contains the names of the files selected. So I can add some extra code in to write this to disk. That works fine. But I dont want to write it to disk. I want to pass it back to the original functions that called this. The arguments for ReportSelectedItems can be altered, but IFACEMETHODIMP OnFileOk(IFileDialog *pfd) cannot as it is inherited. Adding a vector& file_names to the argument will stop it compiling.
So how should I deal with this? I could use a global variable for file_names, but everything I am learning about programming is telling me not to. It would be a quick fix, but I worry that would encourage me to be lazy in the future. I find it difficult to read the windows code and I don't really want to delve too much into the details of it. I can't even find what is calling the OnFileOk function, even though I know it is from one of the two base classes.
Do I really need to work at understanding all the library code just to get this one function doing what I'd like? Is there an faster way of going about this?
So to summarize, how would I get information from this inherited function without using a global variable or writing to disk? As I mentined before, I don't have much of a grasp of the code I am working with. And for future reference, how should I deal with this type of situation? I use c++ and would like to avoid c# and c as much as possible.
Thanks as always.
It seems a fairly big omission for Microsoft to have left out any sort of user data associated with the IFileDialog callbacks, but that does seem to be the case.
I'm assuming that simply calling GetSelectedItems() once the dialog returns is something you don't want to do for some reason - because that would obviously be the simplest solution.
From a quick look at the docs one way you may be able to pass data back from the event callback is using the owner window that you pass to IFileDialog::Show() (which is actually IModalWindow::Show()).
In the event handler, you get given the IFileDialog* pointer. From this, you can QI the address of the IOleWindow interface which will give you the dialog's window:
IFACEMETHODIMP OnFileOk(IFileDialog *pfd)
{
CComPtr<IOleWindow> pWindow;
if (SUCCEEDED(pfd->QueryInterface(IID_IOleWindow, reinterpret_cast<void**>(&pWindow))))
{
HWND hwndDlg;
if (SUCCEEDED(pWindow->GetWindow(&hwndDlg)))
{
HWND hwndOwner;
if (hwndOwner = GetWindow(hwndDlg, GW_OWNER))
{
// hwndOwner is the owner window of the dialog
}
}
}
// more code
}
Now assuming that hwndOwner is your own window, you can associate any data you like with it using SetProp()/GetProp() - so you could use this as a mechanism to pass data back from within the callback.
A simple solution was to add member data inside the inherited class and link it from the constructor:
class CFileOpenBasketPickerCallback : public IFileDialogEvents, public IFileDialogControlEvents
{
public:
CFileOpenBasketPickerCallback(vector<wstring>& files) : files_(files)
{
}
// functions
private:
vector<wstring>& files_;
};
When constructing the object
vector<std::wstring> files
CFileOpenBasketPickerCallback foacb(files);
And in IFACEMETHODIMP OnFileOk(IFileDialog *pfd)
ReportSelectedItems(pfd, psia, files_);
ReportSelectedItems is not a member so you can alter the arguments.
While trying to create a nice wrapper around Win32 specific GUI components, I eventually ran into a problem. The problem is that I'm unable to close the application after the windows I created no longer exist.
My API works like this:
/// ----------------------------
/// #author God
/// #project Helixirr Widgets
/// ----------------------------
#include <helixirrwidgets/HelixirrWidgets.hpp>
int main(void){
HelixirrWidgets::Window __windows[2] = {HelixirrWidgets::Window("Big Window"), HelixirrWidgets::Window()};
__windows[0].position(200, 200);
__windows[0].size(800, 600);
__windows[0].visible(true);
__windows[0].save_changes();
__windows[1].name("Tiny Window");
__windows[1].position(10, 100);
__windows[1].size(400, 200);
__windows[1].visible(true);
__windows[1].save_changes();
while(__windows[0].active() || __windows[1].active()){
if(__windows[0].visible()){
__windows[0].show();
}
if(__windows[1].visible()){
__windows[1].show();
}
}
return 0;
}
In method of HelixirrWidgets::Window called "active", which is declared like this
inline bool active(void) const noexcept;
I can check, whether my window is active or not.
This method basically return a const reference to a boolean member variable of an instance. This member variable is modified in "show"-method of the same class. Here's the definition:
void Window::show(void){
if(GetMessage(&_m_opHelper->message, _m_opHelper->handle_window, 0, 0)){
if(_m_opHelper->message.message == WM_CLOSE){
_m_bActive = false;
return;
}
TranslateMessage(&_m_opHelper->message);
DispatchMessage(&_m_opHelper->message);
ShowWindow(_m_opHelper->handle_window, SW_SHOWDEFAULT);
UpdateWindow(_m_opHelper->handle_window);
_m_bActive = true;
return;
}
_m_bActive = false;
}
Do note I use pimpl-idiom to hide platform-specific structures ("_m_opHelper" is pointer to implementation).
It may look like it works, but it doesn't and I can't understand why. It all comes down to a simple question: how can I close my window implemented using WINAPI specific functions and structures to be closed appropriately by a user of my application?
I guess the cause of the issue is related to the fact WM_CLOSE simply is not last message HWND gets. Messages like WM_DESTROY, WM_NCDESTROY and possibly more (depending on the particlar window and its state) will come after WM_CLOSE, leading to the assignment _m_bActive = TRUE.
I.e. the window becomes inactive for very short time, and (likely) they will never be inactive at the same time, causing an endless loop in main().