Gtkmm Box Not Being Removed - c++

I'm creating an application using gtkmm, where I create a number of Gtk::Boxes then display them as need be to the screen. These boxes display Gtk::Entry widgets to get information from the user, along with other widgets.
My problem is that when I try and "remove" these boxes, they don't seem to actually get removed, so they take up memory which results in my application crashing. I'm unsure of why it's doing this, but what I find interesting is this: every time my "remove_widget" function (which removes the Gtk::Box I want removed) executes, I have a std::cout statement which displays "Container Removed". Each time, it should theoretically only remove the container element (the Gtk::Box). But each subsequent time it runs, the number of "Container Removed" statements doubles. So the first time it gets displayed once, the next time it gets displayed twice, then four times, then eight, etcetera.
Anyways, the core of what I want to do is add a specific box to the window, get information from it, then destroy it should I can add a different Gtk::Box. I don't know if this method is the best way to do this, but as of right now it's how I'm going about it. Anyways, here is the code to get you an idea. What's happening is that I load MainWin, which creates a new window. Then MainWin packs an Event_Controller box into it, and the Event_Controller handles displaying the other boxes I want to display for the user. on_new_flight_clicked() handles the displaying the first box, and on_next_clicked handles the displaying the rest of the boxes.
class MainWin : public Gtk::Window {
public:
MainWin();
virtual ~MainWin();
protected:
// signal handler
void on_button_clicked(Glib::ustring data);
// Flight Data Object
Flight flight;
// Member widgets:
Gtk::Box main_container_box; // Holds everything in the window
Gtk::Button new_comp; // Allows the user to create a new competition
Gtk::Button go_home; // Allows the user to return to the main
screen
Event_Controller* new_event;
};
class Event_Controller : public Gtk::Box {
public:
Event_Controller(Flight& tflighto);
virtual ~Event_Controller();
protected:
Flight* flight;
Gtk::Box* container_box;
void on_new_flight_clicked();
void on_next_clicked(Glib::ustring data);
void remove_widget();
Gtk::Button next; // This will appear at every step of the create a
flight sequence, except the end, taking the user to the next step
};
void Event_Controller::on_new_flight_clicked()
{
container_box = new Gtk::Box; // Create a new container box
add(*container_box); // Add it to the box
container_box->pack_start(*Gtk::manage(new Flight_Letter(flighto)),
Gtk::PACK_EXPAND_WIDGET); // Pack the first form into the container
box
add(next); // Pack the next button outside the container box
show_all_children(); // Show everything
}
void Event_Controller::remove_widget()
{
// Remove everything from Event_Controller, this is where I think* things go wrong
remove(*container_box);
delete container_box;
container_box = nullptr;
cout << "Container Removed..." << endl;
remove(next);
container_box = new Gtk::Box; // Create the container box
add(*container_box);
}
void Event_Controller::on_next_clicked(Glib::ustring data)
{
int figures_loop = 0;
// This will show the Flight_Number form. Here is the general model
for what we do in this function. This function
// gets passed an argument, and that allows us to remove the form we
need to and bring up a new form.
if(data == "first")
{
// First we remove the previous form and the container
remove_widget();
// Then we set next to send a message to this function when it's
clicked, so that it knows what form to bring up next
next.signal_clicked().connect(sigc::bind<Glib::ustring>
(sigc::mem_fun(*this, &Event_Controller::on_next_clicked), "first"));
// Create the new form
container_box->pack_start(*Gtk::manage(new
Figure_Number(num_of_figures)), Gtk::PACK_EXPAND_WIDGET);
// Pack the next button
pack_start(next);
// Show all the widgets
show_all_children();
}
// There is more to this function, I just left it out for brevity sake
}
Thanks for any help ahead of time, and if anything is unclear or you have any suggestion/concerns/comments, feel free to comment further.

Related

MFC MDI Collecting control states for the "apply" button routine

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.

Typecasting EventHandler in C++/CLI

I have a form (which I'll call MainForm) embedded with a TabControl. Every time the user creates a new tab it is filled with an instance of a pre-built Panel (which I'll call MyPanel) which contains many controls.
My MyPanel class has a private variable bool save_state which is set to false every time one of the (editable) controls is edited and set to true when the user "saves" the state of the panel.
I want a visual flag to keep track of tabs that have unsaved changes (e.g. the tab "Tab1" will instead display the text "Tab1 *" if it has unsaved changes). So I want to set up the event handler in my MainForm which can call a method in MyPanel to add the handler to each control.
Since not all my controls use the same EventHandler type (for example, I also need to track DataGridViewRowsAddedEvent, among others), I currently have several methods adding the appropriate handler to the corresponding controls (one for each type of Event Handler), each of which is running the same code (i.e. set the save_state bit to false and append " *" to the tab text.
For example, in MainForm.cpp I have:
#include "MyPanel.h"
void markUnsaved(void) {
// set panel bit to false
// append " *" to tab text if we haven't already
}
void MainForm::handler1(Object ^sender, EventArgs ^e) {
markUnsaved();
}
void MainForm::handler2(Object ^sender, DataGridViewRowsAddedEventArgs ^e) {
markUnsaved();
}
void Main::FormaddNewPanelToTab(int tab_index) {
// check index is valid ...
// make the new panel
MyPanel ^new_panel = gcnew MyPanel();
new_panel->addEventHandlerToControls(gcnew EventHandler(this, &MainForm::handler1));
new_panel->addDgvEventHandlerToControls(gcnew DataGridViewRowsAddedEventHandler(this, &MainForm::handler2));
// rest of code...
}
Though this currently works as intended, this (along with the several other Event Handler types I have to manage) makes my code look really silly.
I am hoping to be able to have have a single event handler in MainForm and a single method in MyPanel which type-casts the Event Handler passed and adds it to all the controls with the appropriate types.
I have tried doing simple casts such as:
void MyPanel::addHandlerToControls(EventHandler ^handler) {
control_NUD->ValueChanged += handler; // this works because ValueChanged is of type EventHandler
control_DGV->RowsAdded += (DataGridViewRowsAddedEventHandler ^)handler; // this compiles but throws an exception
// rest of the code...
}
to no avail.
Any help would be greatly appreciated!
I know this is maybe a bit late for answer but I'd want to show how would I solve this.
Firs of all I suggest to get rid from idea of casting event handlers. Kind of such approach may work in C# (with some adjustments) but as far as I know it's not possible in C++ /CLI.
I'd go for adding new event to a MyPanel class that will be invoked every time when the data on a panel is changed. But to avoid adding a lot of different handlers to a control events in a MyPanel class it's better to create one generic method that will handle all the neccessary control's events and fire new event. Maybe this sounds messy, let me show the code:
public ref class MyPanel
{
// Add a new event
public:
event EventHandler^ DataChanged;
// Add a method that will fire new event
// this methid will be invoked on every control's event that you'll subscribe
private:
generic <typename T>
void DataChangedHandler(System::Object^ sender, T e)
{
// Fire the event
DataChanged(this, EventArgs::Empty);
}
// Once the controls are initialized you may add the event handlers
// I put it in a constructor only for example
MyPanel()
{
control_NUD->ValueChanged += gcnew EventHandler(this, &MyPanel::DataChangedHandler<EventArgs^>);
control_DGV->RowsAdded += gcnew DataGridViewRowsAddedEventHandler(this, &MyPanel::DataChangedHandler<DataGridViewRowsAddedEventArgs^>);
// and so on...
}
}
/// And now in a main form we only need to subscribe to a DataChanged event
public ref class MainForm
{
//...
// the handler
void MyHandler(Object^ sender, EventArgs^ e)
{
markUnsaved();
}
void FormaddNewPanelToTab(int tab_index)
{
// make the new panel
MyPanel ^new_panel = gcnew MyPanel();
new_panel->DataChanged += gcnew EventHandler(this, &MainForm::MyHandler);
}
//...
}
Hope this helps.

How to pass multiple variables from QT Dialog to Main Window

I have a press button (pushButton_RenameTargets) and 3 labels (label_Tar1ex, label_Tar2ex, label_Tar3ex) on my main form with default text values. When I push the button (pushButton_RenameTargets) a dialog is created (renametargets). It has three text edit lines (lineEdit_Target1, lineEdit_Target2,lineEdit_Target3). When I enter names on the three text edit lines and push OK I want the 3 labels on my main form to update.
Better Described:
When the button is pressed:
void MainWindow::on_pushButton_RenameTargets_clicked()
{
RenameTargets renametargets;
renametargets.setModal(true);
renametargets.exec();
}
It creates the dialog window renametargets.
Window has three text edit lines (lineEdit_Target1, lineEdit_Target2,lineEdit_Target3).
When the OK button is pushed I store the text in QString variables.
void RenameTargets::on_buttonBox_TargetRename_accepted()
{
QString Target1NameInput = ui->lineEdit_Target1->text();
QString Target2NameInput = ui->lineEdit_Target2->text();
QString Target3NameInput = ui->lineEdit_Target3->text();
}
Questions:
(1) How can I set the text of QString Target1NameInput (located on second form: renametargets) to label_Tar1ex (located on main form) as I push the OK button on the dialog.
(2) How can I get to display label_Tar1ex (located on main form) to display on a label in the second form -- called label_CurrentName_Tar1ex.
Basically this is a renaming scheme.....
What I would do is declare Target1NameInput and others in your dialog's class instead of your Ok function. That way those variables always "exist" while your dialog exists. If you create them in your Ok function, then they vanish when that function ends, and then you can't get them from your mainWindow anymore.
Move the variable declarations to your dialog's class. (They go in public so other classes can get at em)
class RenameTargets : public QDialog
{
Q_OBJECT
public:
QString Target1NameInput; //Side note, variable naming convention says
QString Target2NameInput; //that variables should start with a lowercase
QString Target3NameInput; //letter, but totally up to you ;)
//Your other class stuff goes here
}
From that point you can set those variables in your dialog when Ok is pressed.
void RenameTargets::on_buttonBox_TargetRename_accepted()
{
Target1NameInput = ui->lineEdit_Target1->text();
Target2NameInput = ui->lineEdit_Target2->text();
Target3NameInput = ui->lineEdit_Target3->text();
}
And lastly, access those variables in your mainWindow.
void MainWindow::on_pushButton_RenameTargets_clicked()
{
RenameTargets renametargets;
renametargets.setModal(true);
if(renametargets.exec() == QDialog::Accepted) //Check if they clicked Ok
{
ui->label_Tar1ex->setText(renametargets.Target1NameInput);
ui->label_Tar2ex->setText(renametargets.Target2NameInput);
ui->label_Tar3ex->setText(renametargets.Target3NameInput);
}
}
As for your second question, sending from mainWindow to dialog, you have 2 options as I see it.
Set your string variables we created in your dialog class before exec().
Pass the text in your dialog constructor.
If option 1, then you simply call renametargets.Target1NameInput = ui->label_Tar1ex->text(); for each variable before you call renametargets.exec(); Then in your dialog's ui setup, you set your lineEdits text to those same variables.
Let me know if you want me to explain option 2 for you. ;)
There's also many other options to send variables between classes, this is just 1 of those ways. I believe the conventional thing to do would be to have get and set functions within your dialog class, but for my own personal projects, I find that overkill. Up to you.
if (editDocumentDialog->exec() == QDialog::Accepted)
{
editDocumentDialog->getDataRecord(theDocRecord);
documents->updateRecord(theDocRecord);
}
Why not using signal / slot?
void MainWindow::on_pushButton_RenameTargets_clicked()
{
RenameTargets renametargets;
connect(&renametargets, SIGNAL(name_inputted), this, SLOT(update_name_fields);
...
}
Then emit the signal in on_buttonBox_TargetRename_accepted, and update label_Tar1ex... in slot function. You may want to create RenameTargets in heap so it isn't destroyed immediately after its OK is clicked.

Why CListCtrl Update() not necessary in the example below?

In the code below the ListCtrl will not update the item if i do not use "Update()" after the first "If" conditon but will update even if there is no "Update()" method called after the Second "If" conditon. Why is this? I'm just curious to know when Update() is necessary and when it is not!
class MyDialog()
{
public:
void MyFunction();
private:
CListCtrl myListControl;
}
void MyDialog::Myfunction()
{
bool bCondition;
for (auto i = 0, i < myListControl.GetItemCount(); ++i)
{
auto n = myListControl.SetItemText(i, 1, "Start");
if (n)
myListControl.Update(i);
/*Update() is required here */
EvaluateCondition( bConditon);
if(bConditon)
myListControl.SetItemText(i, 1, "End");
/* Why is Update() ***Not*** required here? */
}
}
Update causes the change to be put on the screen immediately. If you don't call it, Windows automatically puts the change on the screen when your message loop next runs (after your MyFunction exits). This is why you need to call it to see "Start", before you change it to "End". Windows automatically updates it to "End" when your function exits.
How can you answer question Why X is true? if X is false???
When you set some item's text - the list control invalidates the corresponding area; eventually, when it comes to painting - it will redraw the new text.
According to MSDN (CListCtrl::Update):
Forces the list view control to repaint the item specified by nItem.
So you can see the result immediately.

QGraphicsView missing text on some draws

I've tried drawing a rectangle with text inside in a QGraphicsView. I get the text from the currently selected item in a QTreeWidget. The scene seems to sporadically show the text, sometimes it will, sometimes it won't.
void MainWindow::on_treewidget_itemSelectionChanged()
{
drawSectionFromProperties(ui->treewidget->currentItem());
}
void MainWindow::drawSectionFromProperties(QTreeWidgetItem *section)
{
ui->graphicsview->setScene(new QGraphicsScene());
ui->graphicsview->scene()->addRect(0,0,200,300,QPen(QColor(0,0,0)),QBrush(QColor(255,250,129)));
QFont objectTitle;
ui->graphicsview->scene()->addSimpleText(section->text(0),objectTitle);
}
Hmm, looks like you are creating a new scene on each item selection?
This isn't a very nice way to go :)
Better do the following:
Create an 'QGraphicsScene* m_scene;' and 'QGraphicsSimpleTextItem* m_textItem' data members in your MainWindow class private section.
In MainWindow::drawSectionFromProperties() do something like:
MainWindow::MainWindow(QWidget* parent, ...)
: m_scene(0), m_textItem(0)
{
...
}
// leave your on_treewidget_itemSelectionChanged as is
void MainWindow::drawSectionFromProperties(QTreeWidgetItem *section)
{
// setup things only ONE time on the first call
if(!m_scene)
{
m_scene = new QGraphicsScene();
ui->graphicsview->setScene(m_scene);
m_textItem = ui->graphicsview->scene()->addSimpleText(QString());
}
// here only change text of existing item
m_textItem->setText(section->text(0));
}
This way you won't be creating new scene on every item selection. Actually you need ONE scene and ONE item in it, no need to create them over and over and over again and stack one onto another like you currently do.
Cheers :)