How to change z-order of wxTextCtrl?

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
kathbeau
Knows some wx things
Knows some wx things
Posts: 48
Joined: Fri Nov 28, 2014 6:30 pm

How to change z-order of wxTextCtrl?

Post by kathbeau » Fri Mar 15, 2019 5:13 pm

I have a wxBoxSizer into which I add many instances of custom wxBoxSizer objects, each of which contains labels and one wxTextCtrl.

I have implemented a feature that lets the user right-click on one of the custom objects' wxTextCtrl and select "move item to the top". Wonder of wonders, it worked! But now the z-order for the collection is wonky, so tabbing out of the text ctrl of a moved sizer goes to the custom sizer following the original position of the moved sizer.

I tried to use wxTextCtrl->MoveBeforeInTabOrder() using the argument for the wxTextCtrl in the custom sizer in position 1 (because I moved the custom sizer to position 0):

Code: Select all

void TMyFrame::MoveSizerToTop(TMyCustomSizer *custom_sizer)
{
    // A bunch of stuff not relevant to this question happens, then...
    wxBoxSizer * t_main_sizer = ... // code to select the containing sizer
    if (t_main_sizer->Detach(custom_sizer))
    {
        t_main_sizer->Prepend(custom_sizer, wxEXPAND | wxALL);
        t_main_sizer->Layout();
        if (t_main_sizer->GetItemCount() > 1)
        {
            TMyCustomSizer *t_next_sizer = (TMyCustomSizer *) t_main_sizer->GetItem(1);
            custom_sizer->GetMyTextCtrl()->MoveBeforeInTabOrder(next_sizer->GetMyTextCtrl()); // violation!
        }
     }
}
... but void wxWindowBase::DoMoveInTabOrder(wxWindow *win, WindowOrder move) understandably complained
wxCHECK_RET( i, wxT("MoveBefore/AfterInTabOrder(): win is not a sibling") );

Would it be possible for me to set the z-order directly? That is, does something exist that lets me do this:

Code: Select all

for (size_t ui=0; ui < t_main_sizer->GetCount(); ui++)
{
	TMyCustomSizer *t_next_sizer = (TMyCustomSizer *)t_main_sizer->GetItem(ui);
	t_next_sizer->GetMyTextCtrl()->SetTabOrder(ui);    // SetTabOrder is a name I made up
}
I may have programmed myself into a corner... and will have to change the class of my custom sizers to something descending from wxWindow, but I am really hoping that won't be necessary.

Thanks,
Kathleen

Manolo
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 698
Joined: Mon Apr 30, 2012 11:07 pm

Re: How to change z-order of wxTextCtrl?

Post by Manolo » Fri Mar 15, 2019 5:37 pm

The window you want to change "tab order" custom_sizer->GetMyTextCtrl() and the reference (another window, next_sizer->GetMyTextCtrl()) must be siblings, they must have the same parent.

Remember: a sizer is NOT a window, but a "machine" that handles sizes and positions.

BTW, using a wxPanel to place controls instead of wxFrame does allow the tab-key proccesing.

ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 3464
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: How to change z-order of wxTextCtrl?

Post by ONEEYEMAN » Fri Mar 15, 2019 6:01 pm

Hi,
I would be curious to know how those text controls and labels were created?
Can you post such code?

Thank you.

kathbeau
Knows some wx things
Knows some wx things
Posts: 48
Joined: Fri Nov 28, 2014 6:30 pm

Re: How to change z-order of wxTextCtrl?

Post by kathbeau » Fri Mar 15, 2019 6:06 pm

The window you want to change "tab order"...must be siblings, they must have the same parent.
Right. But at creation they are being assigned some sort of z-order, because when I build the MyCustomSizer objects and place them in the main sizer, pressing the Tab key advances down through the sizers' text controls just as a user would expect.

If that statement ("they are being assigned some sort of z-order") is not true, then what is giving me the happy experience of tabbing from one sizer/text control to the next?
Remember: a sizer is NOT a window, but a "machine" that handles sizes and positions.

BTW, using a wxPanel to place controls instead of wxFrame does allow the tab-key proccesing.
Yes, I know. I'm presently talking myself into rewriting the classes involved. <whimper>

Thanks,
Kathleen

ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 3464
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: How to change z-order of wxTextCtrl?

Post by ONEEYEMAN » Fri Mar 15, 2019 6:58 pm

Hi,
First of all the term "z-order" is not applicable here. The proper term is TAB-order.
Second - when you press TAB key the focus is moving from the control you created first to the second, third, etc. I.e.:

wxTextCtrl *text1 = new wxTextCtrl();
wxTextCtrl *text2 = new wxTextCtrl();
wxTextCtrl *text3 = ne2w wxTextCtrl();

When the program starts the focus is on the text1. When you press TAB focus moves to text2, etc.

And thats why I'd like to see how you created those controls and the dialog/frame in general.

Thank you.

kathbeau
Knows some wx things
Knows some wx things
Posts: 48
Joined: Fri Nov 28, 2014 6:30 pm

Re: How to change z-order of wxTextCtrl?

Post by kathbeau » Fri Mar 15, 2019 7:45 pm

And thats why I'd like to see how you created those controls and the dialog/frame in general.
Well, it's a lot of code, but here you go:

First is the class that creates the custom sizers.

Code: Select all

#ifndef DISPLAYDATASIZER_H
#define DISPLAYDATASIZER_H

#pragma once

#include <wx/sizer.h>

class TXMLDataForm;
class TItemDef;
class TDisplayDataSizer;

enum 
{ 
	POP_RESTORE = 3000,
        POP_MOVE_TO_TOP,
	POP_SHOW_DEF
};

class MyTextCtrl : public wxTextCtrl
{
private:
	TXMLDataForm       *FDataForm;
	TItemDef           *FItemDef;
	TDisplayDataSizer  *FDisplaySizer;
public:
	MyTextCtrl(wxWindow *parent, wxWindowID id, const wxString &value,
		const wxPoint &pos, const wxSize &size, int style = 0, TDisplayDataSizer *display_sizer = NULL)
		: wxTextCtrl(parent, id, value, pos, size, style)
	{
		FDataForm = NULL;
		FItemDef = NULL;
		FDisplaySizer = display_sizer; // needed for OnMoveSizerToTop
	}

	void SetDataForm(TXMLDataForm* data_form) { FDataForm = data_form; }
	void SetItemDef(TItemDef* item_def) { FItemDef = item_def; }

	void OnExitTextCtrl(wxFocusEvent& event); // for FValueText on DisplayDataSizer
	void OnPopupMenu(wxContextMenuEvent& event);

	void OnRestoreOriginal(wxCommandEvent& event);
	void OnMoveSizerToTop(wxCommandEvent& event);


	wxDECLARE_EVENT_TABLE();
};

class TDisplayDataSizer : public wxBoxSizer
{
public:
	TDisplayDataSizer(wxScrolledWindow* parent, int orient, TItemDef* item_def, TXMLDataForm* data_form);
	~TDisplayDataSizer() { ; }

	// getters and setters
	wxBoxSizer* GetItemNameSizer() { return FItemNameSizer; }
	wxBoxSizer* GetNumIdSizer() { return FNumIdSizer; }
	wxBoxSizer* GetValueSizer() { return FValueSizer; }

	wxPanel* GetPanel() { return FPanel; }

	TItemDef* GetItemDef() { return FItemDef; }
	MyTextCtrl* GetMyTextCtrl() { return FValueText; }

	wxString GetNaaccrNameLabel() { return FNaaccrNameLabel->GetLabel(); }
	wxString GetNaaccrNumLabel() { return FNaaccrNumLabel->GetLabel(); }
	wxString GetNaaccrIdLabel() { return FNaaccrIdLabel->GetLabel(); }
	wxString GetValueText() { return FValueText->GetValue(); }

	void SetNaaccrNameLabel(wxString label) { FNaaccrNameLabel->SetLabel(label); }
	void SetNaaccrNumLabel(wxString label) { FNaaccrNumLabel->SetLabel(label); }
	void SetNaaccrIdLabel(wxString label) { FNaaccrIdLabel->SetLabel(label); }
	void SetValueText(wxString text) { FValueText->ChangeValue(text); }
	void SetValueTextForegroundColour(bool is_original) 
	{ 
		if (is_original)
			FValueText->SetForegroundColour(*wxBLACK);
		else
			FValueText->SetForegroundColour(*wxRED);
	}


private:
	wxScrolledWindow *FParent;
	TXMLDataForm     *FDataForm;

	TItemDef *FItemDef;
	wxPanel  *FPanel;
	wxBoxSizer *FPanelSizer;
	wxBoxSizer *FItemNameSizer;  // Horizontal with two static texts "Item: naaccrName"
	wxBoxSizer *FNumIdSizer;     // Horizontal with two static texts "Number: naaccrNum  Id: naaccrId
	wxBoxSizer *FValueSizer;     // Horizontal with one text ctrl "Original value"

	wxStaticText *FNaaccrNameLabel;  // i.e., "Date of Diagnosis", not "Item:"
	wxStaticText *FNaaccrNumLabel;   // i.e., "390", not "Num:"
	wxStaticText *FNaaccrIdLabel;    // i.e., "dateOfDiagnosis", not "Id:"
	
	MyTextCtrl   *FValueText;

	wxString stringReplace(const wxString instring, wxString oldpatt, wxString newpatt);
};

#endif

#include <wx/wxprec.h>

#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include <wx/string.h>
#include <wx/colour.h>
//#include <wx/textctrl.h>
//#include <wx/window.h>

#include "DisplayDataSizer.h"
#include "XMLDataForm.h"

//--------------------------------------------------------------------
// MyTextCtrl
//--------------------------------------------------------------------
wxBEGIN_EVENT_TABLE(MyTextCtrl, wxTextCtrl)
    EVT_KILL_FOCUS(MyTextCtrl::OnExitTextCtrl)
	EVT_CONTEXT_MENU(MyTextCtrl::OnPopupMenu)
	EVT_MENU(POP_RESTORE, MyTextCtrl::OnRestoreOriginal)
	EVT_MENU(POP_MOVE_TO_TOP, MyTextCtrl::OnMoveSizerToTop)
wxEND_EVENT_TABLE()

void MyTextCtrl::OnExitTextCtrl(wxFocusEvent& event)
{
	if (this->FDataForm && this->FItemDef)
	{
		bool t_changed = false;
		wxString t_value = this->GetValue();
		FDataForm->UpdateOnExitTextCtrl(t_value, FItemDef, t_changed);
		if (t_changed)
		{
			this->SetForegroundColour(*wxRED);
		}
	}

	event.Skip();
} // OnExitTextCtrl

void MyTextCtrl::OnPopupMenu(wxContextMenuEvent& event)
{
	wxMenu menu;
	menu.Append(POP_RESTORE, "&Restore original value");
	menu.Append(POP_MOVE_TO_TOP, "Move item to top of display");
	menu.Append(POP_SHOW_DEF, "Show dictionary definition");
	PopupMenu(&menu, ScreenToClient(event.GetPosition()));

	// Do not call event.Skip(), because for some reason
	// in this situation, it goes ahead and calls up the chain.
	// Isn't that what Skip() is intended to prevent?
	// Anyway, this is what doublemax told me to do, and it works.
	//event.Skip();
} // OnPopupMenu

void MyTextCtrl::OnRestoreOriginal(wxCommandEvent& event)
{
	wxString t_value = this->GetValue();
	if (FDataForm->RestoreOriginalValue(FItemDef, t_value))
	{
		this->ChangeValue(t_value);
		this->SetForegroundColour(*wxBLACK);
	}

	event.Skip();
}

void MyTextCtrl::OnMoveSizerToTop(wxCommandEvent& event)
{
	FDataForm->MoveSizerToTop(FDisplaySizer);
	
	event.Skip();
}

//--------------------------------------------------------------------
// TDisplayDataSizer
//--------------------------------------------------------------------

TDisplayDataSizer::TDisplayDataSizer(wxScrolledWindow* parent, int orient, TItemDef* item_def, TXMLDataForm* data_form)
	: wxBoxSizer(orient)
{
	FParent = parent;
	FItemDef = item_def;
	FDataForm = data_form;

	FPanel = new wxPanel(FParent);
	
	FPanelSizer = new wxBoxSizer(orient); // wxVERTICAL

	FPanel->SetSizer(FPanelSizer);
	this->Add(FPanel, 0, wxEXPAND | wxALL, 5);

	wxColour my_dim_gray = wxColour(66, 66, 66);
	wxColour my_cadet_blue = wxColour(37, 62, 63);
	wxColour my_cornflower_blue = wxColour(39, 58, 93);
	wxColour my_dark_cyan = wxColour(0, 55, 55);
	wxColour my_brown = wxColour(55, 0, 0);

	// assemble the sizer
	FItemNameSizer = new wxBoxSizer(wxHORIZONTAL);
	wxStaticText *t_label = new wxStaticText(FPanel, wxID_ANY, "Item:");
	t_label->SetForegroundColour(my_brown);
	FItemNameSizer->Add(t_label, 0, wxRIGHT, 5);
	FItemNameSizer->AddSpacer(8);

	FNaaccrNameLabel = new wxStaticText(FPanel, wxID_ANY, FItemDef->FieldName);
	FItemNameSizer->Add(FNaaccrNameLabel, 1, wxEXPAND | wxLEFT | wxRIGHT, 5);

	FPanelSizer->Add(FItemNameSizer);

	FNumIdSizer = new wxBoxSizer(wxHORIZONTAL);

	t_label = new wxStaticText(FPanel, wxID_ANY, "Number:");
	t_label->SetForegroundColour(my_brown);
	FNumIdSizer->Add(t_label, wxRIGHT, 5);
	FNumIdSizer->AddSpacer(8);
	FNaaccrNumLabel = new wxStaticText(FPanel, wxID_ANY, wxString::Format("%i", FItemDef->FieldNumber));

	FNumIdSizer->Add(FNaaccrNumLabel);
	FNumIdSizer->AddSpacer(16);

	t_label = new wxStaticText(FPanel, wxID_ANY, "NaaccrId:");
	t_label->SetForegroundColour(my_brown);
	FNumIdSizer->Add(t_label, 0, wxRIGHT, 5);
	FNumIdSizer->AddSpacer(8);
	FNaaccrIdLabel = new wxStaticText(FPanel, wxID_ANY, FItemDef->NaaccrId);

	FNumIdSizer->Add(FNaaccrIdLabel);

	FPanelSizer->Add(FNumIdSizer);

	FValueSizer = new wxBoxSizer(wxHORIZONTAL);
	FValueText = new MyTextCtrl(FPanel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER, this);

	wxString t_sizer(FItemDef->Length, 'W');
	FValueText->SetInitialSize(
		FValueText->GetSizeFromTextSize(
		FValueText->GetTextExtent(t_sizer)));

	FValueText->SetMaxLength(FItemDef->Length);
	FValueText->SetDataForm(FDataForm);
	FValueText->SetItemDef(FItemDef);

	FValueSizer->Add(FValueText, 1, wxEXPAND | wxLEFT | wxRIGHT, 5);

	FPanelSizer->Add(FValueSizer);

} // ctor with ItemDef

This is the code for the wxMDIChildFrame containing the custom sizers (amidst a bunch of other stuff):

Code: Select all

#ifndef XMLDATAFORM_H
#define XMLDATAFORM_H

#pragma once
#include <wx/event.h>
#include <map>
#include <vector>

#include "MDIChildForm.h"


class TItemDef : public wxObject
{
public:
	TItemDef() : wxObject() { ; }
	~TItemDef() { ; }
	wxString NaaccrId;
	int FieldNumber;
	wxString FieldName;
	wxString ParentXMLElement;
	wxString RecordTypes;
	int StartPos;
	int Length;
	int AllowUnlimitedText;
	wxString Contains;
	wxString DataType;
	wxString DictionaryUri;
	wxString Padding;
	wxString Trim;
};

typedef struct
{
	bool IsBase;
	wxString Uri;
	wxString Version;
	wxString Specification;
	wxString Description;
	wxString Xmlns;
} TDictionaryDef;

class TDataItem : public wxObject
{
public:
	TDataItem() : wxObject() { ; }
	TDataItem(TItemDef* item_def) : wxObject() { ItemDef = item_def; }
	~TDataItem() { ; }

	TItemDef *ItemDef;
	wxString OriginalValue;
	wxString ModifiedValue;
};

class TPatient : public wxObject
{
public:
	TPatient() : wxObject() { ; }
	virtual ~TPatient();

	unsigned int PatientOrdinal;
	bool IsHidden;
	bool IsModified;

	// These vectors will hold entries for only those ItemDefs for which
	// this patient has non-blank data.
	std::vector<TDataItem*> FPatientItems;  // sort by TDataItem->ItemDef->FieldNumber

	// FTumorItems is a vector of vectors because a Patient record can have 0-to-many Tumors
	std::vector<std::vector<TDataItem*> > FTumorItems; // sort by TDataItem->ItemDef->FieldNumber

	TDataItem* FindPatientItem(int FieldNumber);
	TDataItem* FindTumorItem(int tumor_ordinal, int FieldNumber);
	TDataItem* FindFirstModifiedDataItem();
};



// forward declarations
class TXmlInterface;
class TDisplayDataSizer;

class TXMLDataForm : public TMDIChildForm
{
private:
	TXmlInterface* FXmlInterface;

	wxBoxSizer *FFormSizer;     // wxHORIZONTAL, listbox to left, TDisplayDataSizers to the right
	wxBoxSizer *FListBoxSizer;  // wxVERTICAL
	wxBoxSizer *FDisplaySizer;  // wxVERTICAL
	

	wxRadioBox *FDataRadioBox;
	bool FShowAllPanels;
	
	wxSpinButton *FTumorSpinButton;
	wxStaticText *FSpinLabel;
	int FCurrentTumorOrdinal;
	wxNotebook *FNotebook;

	wxScrolledWindow *FPatientPage;
	wxScrolledWindow *FTumorPage;
	wxScrolledWindow *FNaaccrDataPage;
	wxBoxSizer       *FPatientSizer;
	wxBoxSizer       *FTumorSizer;
	wxBoxSizer       *FNaaccrDataSizer;

	std::vector<TPatient*> FPatients;
	size_t FMaxSize;
	wxListBox  *FPatientsListBox;

	std::vector<TDataItem*> FNaaccrDataItems; // contains the editable values for NaaccrData items

	std::map<int, TDictionaryDef*> FDictionaryDefs;
	std::map<int, TItemDef*> FItemDefsByNaaccrNum;
	std::map<wxString, TItemDef*> FItemDefsByNaaccrId;

	// I need to read the data according to ParentLevel, so
	// we'll put these into vectors for reading by SetUpPatients()
	std::vector<TItemDef*> FNaaccrDataItemDefs;
	std::vector<TItemDef*> FPatientDataItemDefs;
	std::vector<TItemDef*> FTumorDataItemDefs;

	bool GetDictionariesFromXmlDataFile(wxString dictionaries_folder, wxString xml_file, wxString& base, wxString& user);
	bool SetUpPatients();
	void CreateDisplayPanels();
	void UpdateNaaccrDataControls(bool show_all = false);
	TDataItem* FindNaaccrDataItem(int FieldNumber);
	void UpdatePatientControls(TPatient* patient, bool show_all = false);
	void UpdateTumorControls(TPatient* patient, int tumor_ordinal, bool show_all = false);

	wxPanel* CreateDataControlPanel(TXMLDataForm *parent);
public:
	TXMLDataForm();
	TXMLDataForm(wxMDIParentFrame *parent, wxString filename, wxString dictionaries_dir, TXmlInterface* xml_interface);
	~TXMLDataForm();

	wxString FXmlFileName;
	wxString FDictionariesDir;

	bool FIsModified;

	TPatient* FCurrentPatient; // for use by callback to OpenXmlDataFile
	void AddToPatient(TItemDef* item_def, int tumor_ordinal, wxString value);

	bool Initialize();

	void GetMenuCaps(TMenuCaps&);
	// make the FItemDefs available to the callback
	std::map<int, TItemDef*>& GetItemDefsByNaaccrNum() { return FItemDefsByNaaccrNum; }
	std::map<wxString, TItemDef*>& GetItemDefsByNaaccrId() { return FItemDefsByNaaccrId; }


	static void ShowItemDef(
		void* owner,
		const char* NaaccrId,
		const int NaaccrNum,
		const char* NaaccrName,
		const int StartColumn,
		const int Length,
		const int AllowUnlimitedText,
		const char* Contains,
		const char* RecordTypes,
		const char* ParentXmlElement,
		const char* DataType,
		const char* DictionaryUri,
		const char* Padding,
		const char* Trim);

	static void ReadItemByNaaccrId(
		void* owner,
		const int patient_ordinal,      // the nth Patient
		const int tumor_ordinal,        // the 1st, 2nd, etc. Tumor for the nth Patient
		const char* NaaccrId,
		const int NaaccrNum,
		const char* value);

	void UpdateOnExitTextCtrl(wxString current_value, TItemDef *item_def, bool& is_changed);

	bool RestoreOriginalValue(TItemDef *item_def, wxString& value);
	void MoveSizerToTop(TDisplayDataSizer *display_sizer);


	void OnSetFilter(wxCommandEvent& event);
	void OnClearFilter(wxCommandEvent& event);
	void OnSave(wxCommandEvent& event);
	void OnSaveAs(wxCommandEvent& event);
	void OnRunEDITS(wxCommandEvent& event);
	void OnSelListBox(wxCommandEvent& event);
	void OnDataRadioBox(wxCommandEvent& event);
	void OnSpinTumor(wxCommandEvent& event);

	wxDECLARE_EVENT_TABLE();

};

#endif

// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>
#include <memory>   // std::unique_ptr
#include <fstream>

#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include <wx/notebook.h>
#include <wx/radiobox.h>
#include <wx/spinbutt.h>
#include <wx/busyinfo.h>
#include <wx/evtloop.h>
#include <wx/colour.h>
#include "XMLDataForm.h"
#include "MainFrm.h"
#include "XmlInterface.h"
#include "DisplayDataSizer.h"
#include "ProgressDialog.h"

wxBEGIN_EVENT_TABLE(TXMLDataForm, wxMDIChildFrame)
	EVT_MENU(ID_SetFilter, TXMLDataForm::OnSetFilter)
	EVT_MENU(ID_ClearFilter, TXMLDataForm::OnClearFilter)
	EVT_MENU(ID_Save, TXMLDataForm::OnSave)
	EVT_MENU(ID_SaveAs, TXMLDataForm::OnSaveAs)
	EVT_MENU(ID_RunEDITS, TXMLDataForm::OnRunEDITS)
wxEND_EVENT_TABLE()


TPatient::~TPatient()
{
	std::vector<TDataItem*>::iterator pit = FPatientItems.begin();
	while (pit != FPatientItems.end())
	{
		delete (*pit);
		pit++;
	}

	for (size_t t_idx = 0; t_idx < FTumorItems.size(); t_idx++)
	{
		std::vector<TDataItem*>::iterator tit = FTumorItems[t_idx].begin();
		while (tit != FTumorItems[t_idx].end())
		{
			delete (*tit);
			tit++;
		}
	}
} // TPatient dtor

TDataItem* TPatient::FindPatientItem(int FieldNumber)
{
	TDataItem* t_patient_item = NULL;
	int t_low = 0,
		t_high = FPatientItems.size() - 1,
		t_mid;

	while (t_low <= t_high)
	{
		t_mid = (t_low + t_high) / 2;
		if (FPatientItems[t_mid]->ItemDef->FieldNumber == FieldNumber)
			return FPatientItems[t_mid];
		else if (FPatientItems[t_mid]->ItemDef->FieldNumber > FieldNumber)
			t_high = t_mid - 1;
		else
			t_low = t_mid + 1;
	}

	return t_patient_item;
} // FindPatientItem

TDataItem* TPatient::FindTumorItem(int tumor_ordinal, int FieldNumber)
{
	TDataItem* t_patient_item = NULL;

	if (tumor_ordinal < (int)FTumorItems.size())
	{
		std::vector<TDataItem*> t_tumors = FTumorItems[tumor_ordinal];

		int t_low = 0,
			t_high = t_tumors.size() - 1,
			t_mid;

		while (t_low <= t_high)
		{
			t_mid = (t_low + t_high) / 2;
			if (t_tumors[t_mid]->ItemDef->FieldNumber == FieldNumber)
				return t_tumors[t_mid];
			else if (t_tumors[t_mid]->ItemDef->FieldNumber > FieldNumber)
				t_high = t_mid - 1;
			else
				t_low = t_mid + 1;
		}
	}

	return t_patient_item;
} // FindTumorItem

TDataItem* TPatient::FindFirstModifiedDataItem()
{
	TDataItem* t_data_item = NULL;

	std::vector<TDataItem*>::iterator pit = FPatientItems.begin();
	while (pit != FPatientItems.end())
	{
		if (!(*pit)->ModifiedValue.empty())
		{
			t_data_item = (*pit);
			return t_data_item;
		}
		pit++;
	}

	for (size_t t_idx = 0; t_idx < FTumorItems.size(); t_idx++)
	{
		std::vector<TDataItem*>::iterator tit = FTumorItems[t_idx].begin();
		while (tit != FTumorItems[t_idx].end())
		{
			if (!(*tit)->ModifiedValue.empty())
			{
				t_data_item = (*tit);
				return t_data_item;
			}
			tit++;
		}
	}

	return t_data_item;

} // FindFirstModifiedDataItem

TXMLDataForm::TXMLDataForm(wxMDIParentFrame *parent, wxString filename, wxString dictionaries_dir, TXmlInterface* xml_interface)
	: TMDIChildForm(parent, filename)
{
	FXmlFileName = filename;
	FDictionariesDir = dictionaries_dir;

	FIsModified = false;

	FXmlInterface = xml_interface;

	// FIX ME: TESTING
	FMaxSize = 10000;
	FPatients.reserve(FMaxSize);

} // ctor


TXMLDataForm::~TXMLDataForm()
{
	{
		if (FPatientsListBox)
			FPatientsListBox->Clear();
		
		std::vector<TPatient*>::iterator itx = FPatients.begin();
		while (itx != FPatients.end())
		{
			TPatient* t_temp = (*itx);
			delete t_temp;
			itx++;
		}
	}
	
	{
		std::map<int, TDictionaryDef*>::iterator itx = FDictionaryDefs.begin();
		while (itx != FDictionaryDefs.end())
		{
			delete (*itx).second;
			itx++;
		}
	}

	{
		// We stored the collection of ItemDefs in two maps; just
		// free the objects from one of them.
		std::map<int, TItemDef*>::iterator itx = FItemDefsByNaaccrNum.begin();
		while (itx != FItemDefsByNaaccrNum.end())
		{
			delete (*itx).second;
			itx++;
		}
	}

} // dtor

void TXMLDataForm::GetMenuCaps(TMenuCaps& caps)
{
	wxMenuBar* t_bar = FMainFrame->GetMenuBar();
	wxMenu* t_menu = t_bar->GetMenu(t_bar->FindMenu("&Xml Data"));

	if (FIsModified)
	{
		caps.insert(mcSave);
	}

	if (FIsFiltered)
	{
		caps.insert(mcClearFilter);
	}

	wxMenuItem* t_item = t_menu->FindItem(ID_Save);
	if (t_item)
		t_item->Enable(FIsModified);

	t_item = t_menu->FindItem(ID_ClearFilter);
	if (t_item)
		t_item->Enable(FIsFiltered);

}

bool TXMLDataForm::Initialize()
{
	wxString t_base;
	wxString t_user;
        bool t_result = GetDictionariesFromXmlDataFile(FDictionariesDir, FXmlFileName, t_base, t_user);

	if (t_result)
	{
		if (!FXmlInterface->Initialize(t_base, t_user))
		{
			wxLogMessage("Failure in FXmlInterface->Initialize");
			return false;
		}

		if (!FXmlInterface->GetAllItemDefs(this, ShowItemDef))
		{
			wxLogMessage("Failure in FXmlInterface->GetAllItemDefs");
			return false;
		}

		if (!SetUpPatients())
		{
			wxLogMessage("Process cancelled by user");
			return false;
		}

		FShowAllPanels = false; // initialize to show only what is populated

		FFormSizer = new wxBoxSizer(wxHORIZONTAL);
		SetSizer(FFormSizer);

		FListBoxSizer = new wxBoxSizer(wxVERTICAL);
		FFormSizer->Add(FListBoxSizer, wxSizerFlags().Expand().Border(wxALL, 10));

		FPatientsListBox = new wxListBox(this, wxID_ANY, wxPoint(0, 0), wxDefaultSize, 0, NULL,
			wxLB_SINGLE | wxLB_NEEDED_SB | wxLB_OWNERDRAW); // ??  | wxLB_OWNERDRAW

		FPatientsListBox->Bind(wxEVT_LISTBOX, &TXMLDataForm::OnSelListBox, this);

		FListBoxSizer->Add(FPatientsListBox, wxEXPAND | wxSHAPED);

		FDisplaySizer = new wxBoxSizer(wxVERTICAL);

		FNotebook = new wxNotebook(this, wxNB_TOP);

		int m_hLine = 15;
		size_t m_nLines = 50;

		// Note: SetDoubleBuffered(true) eliminates that screen-flicker during
		// the processing in the OnSelListBox handler
		FPatientPage = new wxScrolledWindow(FNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL | wxALWAYS_SHOW_SB);
		FPatientPage->SetDoubleBuffered(true);
		// the 0 means don't use a horizontal scrollbar; just vertical
		FPatientPage->SetScrollRate(0, m_hLine);
		FPatientPage->FitInside();

		FTumorPage = new wxScrolledWindow(FNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL | wxALWAYS_SHOW_SB);
		FTumorPage->SetDoubleBuffered(true);

		FTumorPage->SetScrollRate(0, m_hLine);
		FTumorPage->FitInside();

		FNaaccrDataPage = new wxScrolledWindow(FNotebook, wxID_ANY, 
		     wxDefaultPosition, wxDefaultSize, wxVSCROLL | wxALWAYS_SHOW_SB);
		FNaaccrDataPage->SetDoubleBuffered(true);

		FNaaccrDataPage->SetScrollRate(0, m_hLine);
		FNaaccrDataPage->FitInside();

		FNotebook->AddPage(FPatientPage, "Patient", true);
		FNotebook->AddPage(FTumorPage, "Tumor");
		FNotebook->AddPage(FNaaccrDataPage, "NaaccrData");

		FPatientSizer = new wxBoxSizer(wxVERTICAL);
		FTumorSizer = new wxBoxSizer(wxVERTICAL);
		FNaaccrDataSizer = new wxBoxSizer(wxVERTICAL);
		FPatientPage->SetSizer(FPatientSizer);
		FTumorPage->SetSizer(FTumorSizer);
		FNaaccrDataPage->SetSizer(FNaaccrDataSizer);

		FNotebook->SetSelection(0);

		wxPanel *t_panel = CreateDataControlPanel(this);
		FDisplaySizer->Add(t_panel, wxSizerFlags().Expand().Align(wxALIGN_TOP));

		FDisplaySizer->Add(FNotebook, 1, wxEXPAND | wxALL);

		FFormSizer->Add(FDisplaySizer, 1, wxEXPAND | wxALL);

		wxString t_temp;
		TPatient* t_first_patient = NULL;
		std::vector<TPatient*>::iterator pit = FPatients.begin();
		while (pit != FPatients.end())
		{
			TPatient* t_patient = (*pit);
			if (!t_first_patient)
				t_first_patient = t_patient;
			t_temp.Printf("Patient #%d", t_patient->PatientOrdinal);
			FPatientsListBox->Append(t_temp, (wxClientData*)t_patient);

			pit++;
		}

		FPatientsListBox->SetSelection(0);

		CreateDisplayPanels();
		UpdateNaaccrDataControls(false);
		if (t_first_patient)
		{
			UpdatePatientControls(t_first_patient, false);
			UpdateTumorControls(t_first_patient, 1, false);
		}
	}

	SetSizer(FFormSizer);

	return t_result;
} // Initialize

bool TXMLDataForm::GetDictionariesFromXmlDataFile(wxString dictionaries_folder, 
		wxString xml_file, wxString& base, wxString& user)
{
	bool t_result = true;

	// This function reads the attributes of the <NaaccrData...> element
	// to extract the baseDictionaryUri and (if it exists) the userDictionaryUri.
	// It then lops off the "http://xxx.org/" that likely is fictional anyway
	// (really... read the XML standard and the NAACCR XML implementation guide)
	// to get the dictionary file name. For our purposes, we then prefix the
	// file name with the path to the folder on this computer (FDictionariesDir)
	// where we have stashed all of our dictionaries, and return that information to
	// the caller so that TXmlInterface::Initialize can open and parse the dictionaries.
	std::unique_ptr<std::ifstream> t_file(new std::ifstream(static_cast<const char*>(xml_file.c_str()),
		std::ios_base::in | std::ios_base::binary));
	if (t_file && !t_file->fail())
	{
		char t_buf[1024];
		t_file->seekg(0, std::ios_base::beg);
		t_file->read(t_buf, 1023);
		if (t_file->good() || t_file->eof())
		{
			wxString t_header = t_buf;
			wxString t_base_uri = "baseDictionaryUri=\"";
			wxString t_user_uri = "userDictionaryUri=\"";
			wxString t_parse, t_scrap;
			// Get the base dictionary
			size_t t_slash,
				t_pos = t_header.find(t_base_uri);
			if (t_pos != std::string::npos)
			{
				t_parse = t_header.substr(t_pos + t_base_uri.Length(), t_header.Length());
				t_pos = t_parse.find("\"");
				if (t_pos != std::string::npos)
				{
					t_scrap = t_parse.substr(0, t_pos);
					// find the right-most forward slash and lop off
					// everything that precedes it
					t_slash = t_scrap.find_last_of("/");
					if (t_slash != std::string::npos)
					{
						base = dictionaries_folder + FMainFrame->GetPathSeparator() +
							t_scrap.substr(t_slash + 1, t_scrap.length());
					}
				}
			}
			if (base.IsEmpty())
			{
				return false;
			}
			// Get the user dictionar(y|ies)
			user = ""; // initialize to empty string
			t_pos = t_header.find(t_user_uri);
			if (t_pos != std::string::npos)
			{
				t_parse = t_header.substr(t_pos + t_user_uri.length(), t_header.length());
				t_pos = t_parse.find("\"");
				if (t_pos != std::string::npos)
				{
					// remove the quotes around the attribute value
					t_parse = t_parse.substr(0, t_pos);
					while (!t_parse.empty())
					{
						t_pos = t_parse.find(" ");
						if (t_pos != std::string::npos)
						{
							t_scrap = t_parse.substr(0, t_pos - 1);
							t_slash = t_scrap.find_last_of("/");
							if (t_slash == std::string::npos)
							{
								// bad format; just ignore this uri
								break;
							}
							if (!user.IsEmpty())
							{
								user += wxString(" ");
							}
							user += dictionaries_folder + FMainFrame->GetPathSeparator() +
								t_scrap.substr(t_slash + 1, t_scrap.length());
							t_parse = t_parse.substr(t_pos + 1, t_parse.length());
						}
						else
						{
							t_scrap = t_parse;
							t_slash = t_scrap.find_last_of("/");
							if (t_slash == std::string::npos)
							{
								// bad format; just ignore this uri
								break;
							}
							if (!user.empty())
							{
								user += wxString(" ");
							}
							user += dictionaries_folder + FMainFrame->GetPathSeparator() +
								t_scrap.substr(t_slash + 1, t_scrap.Length());
							t_parse = "";
						}
					}
				}
			}
		}
	}
	return t_result;
} // GetDictionariesFromXmlDataFile

bool TXMLDataForm::SetUpPatients()
{
	bool t_result = true;
	size_t t_patient_ordinal = 0;
	int t_tumor_ordinal = 0;
	int t_is_eof = 0;
	int t_tumor_count;

	wxString msg;
	msg.Printf("Processing Patient #%d", 1);

	TProgressDialog dialog(this, wxID_ANY, "Parsing XML data file", wxDefaultPosition, wxDefaultSize, wxCAPTION);

	dialog.SetLabelText(msg);

	dialog.SetUpAndShow();


	// iterate the FNaaccrDataItemDefs vector to get that data
	// then...
	FCurrentPatient = NULL;
	if (!FXmlInterface->OpenXmlDataFile(FXmlFileName, this, ReadItemByNaaccrId, NULL))
	{
		wxLogMessage("TXMLDataForm::SetUpPatients - failure to OpenXmlDataFile");
		return false;
	}
	try
	{
		int pat_ordinal = 0;
		bool skip = false;   // if user clicks "Skip", we'll just use what we have
		bool cont = true;    // if user clicks "Cancel", we'll abort

		while (FXmlInterface->ReadNextPatient(&t_is_eof))
		{
			
			++pat_ordinal;

			
			if (pat_ordinal == 1)
			{
				// Set the progress bar
				msg.Printf("Processing Patient #%d", pat_ordinal);

				dialog.Pulse(msg, &skip);
			}
			else if (pat_ordinal % 100 == 0)
			{
				// Use this where VCL used Application->ProcessMessages();
				wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);
				
				msg.Printf("Processing Patient #%d", pat_ordinal);

				cont = dialog.Pulse(msg, &skip);

			}

			if (skip)
			{
				// User wants to look at what we have so far
				break;
			}

			if (!cont) // user clicked Cancel or Skip
			{
				if (wxMessageBox(wxT("Do you really want to cancel?"),
					wxT("Abandon parsing"),  // caption
					wxYES_NO | wxICON_QUESTION) == wxYES)
				{
					t_result = false;
					break;
				}

				cont = true;
			}
			
			// Create a new TPatient
			TPatient* t_patient = new TPatient();
			t_patient->PatientOrdinal = ++t_patient_ordinal;
			t_patient->IsHidden = false;
			t_patient->IsModified = false;

			// Trying to improve efficiency here... reserve room for
			// all of the Patient-level items in the current dictionary
			// collection
			t_patient->FPatientItems.reserve(FPatientDataItemDefs.size());

			// Make this visible to the AddToPatient function
			FCurrentPatient = t_patient;

			TItemDef* t_item_def;

			std::vector<TItemDef*>::iterator pit = FPatientDataItemDefs.begin();
			while (pit != FPatientDataItemDefs.end())
			{
				t_item_def = (*pit);
				if (!FXmlInterface->GetItemDataByNaaccrId(t_item_def->NaaccrId))
				{
					wxLogMessage("TXMLDataForm::SetUpPatients - Cannot get patient item");
					return false;
				}

				pit++;
			}

			if (FXmlInterface->GetPatientTumorsCount(&t_tumor_count))
			{
				for (t_tumor_ordinal = 1; t_tumor_ordinal <= t_tumor_count; t_tumor_ordinal++)
				{
					if (!FXmlInterface->ReadTumor(t_tumor_ordinal))
					{
						wxLogMessage("TXMLDataForm::SetUpPatients - Failure to ReadTumor");
						return false;
					}
					// iterate the FTumorDataItemDefs vector
					std::vector<TItemDef*>::iterator tit = FTumorDataItemDefs.begin();
					while (tit != FTumorDataItemDefs.end())
					{
						t_item_def = (*tit);
						if (!FXmlInterface->GetItemDataByNaaccrId(t_item_def->NaaccrId))
						{
							wxLogMessage("TXMLDataForm::SetUpPatients - cannot get tumor item");
							return false;
						}

						tit++;
					}
				}
			}

			std::vector<TItemDef*>::iterator nit = FNaaccrDataItemDefs.begin();
			while (nit != FNaaccrDataItemDefs.end())
			{
				t_item_def = (*nit);
				if (!FXmlInterface->GetItemDataByNaaccrId(t_item_def->NaaccrId))
				{
					wxLogMessage("TXMLDataForm::SetUpPatients - Cannot get NaaccrData item");
					return false;
				}

				nit++;
			}

			// Sort the TPatientItems by their ItemDef->FieldNumbers
			std::sort(t_patient->FPatientItems.begin(), t_patient->FPatientItems.end(),
				[](TDataItem* const& a, TDataItem* const& b) 
				{ return a->ItemDef->FieldNumber < b->ItemDef->FieldNumber; });

			for (size_t t_idx = 0; t_idx < t_patient->FTumorItems.size(); t_idx++)
			{
				std::vector<TDataItem*> t_vector = t_patient->FTumorItems[t_idx];
				std::sort(t_vector.begin(), t_vector.end(),
					[](TDataItem* const& a, TDataItem* const& b)
				   { return a->ItemDef->FieldNumber < b->ItemDef->FieldNumber; });
			}


			FPatients.push_back(t_patient);

			if (t_patient->PatientOrdinal == FMaxSize)
			{
				FMaxSize += 5000;
				FPatients.reserve(FMaxSize);
			}

			if (t_is_eof)
				break;
		}
	}
	catch (std::runtime_error& e)
	{
		wxLogMessage(e.what());
	}

	FXmlInterface->CloseXmlDataFile();

	return t_result;
} // SetUpPatients

void TXMLDataForm::ShowItemDef(
	void* owner,
	const char* NaaccrId,
	const int NaaccrNum,
	const char* NaaccrName,
	const int StartColumn,
	const int Length,
	const int AllowUnlimitedText,
	const char* Contains,
	const char* RecordTypes,
	const char* ParentXmlElement,
	const char* DataType,
	const char* DictionaryUri,
	const char* Padding,
	const char* Trim)
{
	TXMLDataForm* myself = (TXMLDataForm*)owner;

	TItemDef* t_item_def = new TItemDef();
	t_item_def->NaaccrId = NaaccrId;
	t_item_def->FieldNumber = NaaccrNum;
	t_item_def->FieldName = NaaccrName;
	t_item_def->ParentXMLElement = ParentXmlElement;
	t_item_def->RecordTypes = RecordTypes;
	t_item_def->StartPos = StartColumn;
	t_item_def->Length = Length;
	t_item_def->AllowUnlimitedText = AllowUnlimitedText;
	t_item_def->Contains = Contains;
	t_item_def->DataType = DataType;
	t_item_def->DictionaryUri = DictionaryUri,
		t_item_def->Padding = Padding;
	t_item_def->Trim = Trim;

	myself->GetItemDefsByNaaccrNum()[NaaccrNum] = t_item_def;
	myself->GetItemDefsByNaaccrId()[NaaccrId] = t_item_def;

	// For use by SetUpPatients
	if (t_item_def->ParentXMLElement.compare("Tumor") == 0)
		myself->FTumorDataItemDefs.push_back(t_item_def);
	else if (t_item_def->ParentXMLElement.compare("Patient") == 0)
		myself->FPatientDataItemDefs.push_back(t_item_def);
	else if (t_item_def->ParentXMLElement.compare("NaaccrData") == 0)
		myself->FNaaccrDataItemDefs.push_back(t_item_def);

} // ShowItemDef
//---------------------------------------------------------------------------

void TXMLDataForm::ReadItemByNaaccrId(
	void* owner,
	const int patient_ordinal,      // the nth Patient
	const int tumor_ordinal,        // the 1st, 2nd, etc. Tumor for the nth Patient
	const char* NaaccrId,
	const int NaaccrNum,
	const char* value)
{
	TXMLDataForm* myself = (TXMLDataForm*)owner;
	if (value)
	{
		int t_vlen = std::strlen(value);
		if (t_vlen > 0)
		{
			TItemDef* t_def = myself->GetItemDefsByNaaccrId()[NaaccrId];
			if (t_def)
			{
				myself->AddToPatient(t_def, tumor_ordinal, value);
			}
		} // if (t_vlen > 0)
	}
} // ReadItemByNaaccrId callback
//---------------------------------------------------------------------------
void TXMLDataForm::AddToPatient(TItemDef* item_def, int tumor_ordinal, wxString value)
{
	TDataItem* t_data_item = new TDataItem(item_def);
	t_data_item->OriginalValue = value;
	t_data_item->ModifiedValue = "";

	if (item_def->ParentXMLElement.compare("Patient") == 0)
	{
		//FCurrentPatient->FPatientItems[item_def->NaaccrId] = t_data_item;
		FCurrentPatient->FPatientItems.push_back(t_data_item);
	}
	else if (item_def->ParentXMLElement.compare("Tumor") == 0)
	{
		// There could be 0-to-many Tumors for this FCurrentPatient,
		// so we have to keep lists separated.
		if (tumor_ordinal > 0)
		{
			std::vector<TDataItem*> t_vector;
			if (tumor_ordinal > (int)FCurrentPatient->FTumorItems.size())
			{
				FCurrentPatient->FTumorItems.push_back(t_vector);
				t_vector.reserve(FTumorDataItemDefs.size());
			}
			else
			{
				t_vector = FCurrentPatient->FTumorItems[tumor_ordinal - 1];
			}
			
			//t_vector[item_def->NaaccrId] = t_data_item;
			t_vector.push_back(t_data_item);
			FCurrentPatient->FTumorItems[tumor_ordinal - 1] = t_vector;
		}
	}
	else if (item_def->ParentXMLElement.compare("NaaccrData") == 0)
	{
		// This will happen only once per data file for NaaccrData items
		FNaaccrDataItems.push_back(t_data_item);
	}

} // AddToPatient

void TXMLDataForm::OnSetFilter(wxCommandEvent& event)
{
	// Call a dialog for setting the filters

	// TESTING

	TPatient* t_first_patient = NULL;
	FIsFiltered = true;
	FPatientsListBox->Clear();

	std::vector<TPatient*>::iterator itx = FPatients.begin();
	while (itx != FPatients.end())
	{
		TPatient* t_patient = (*itx);
		int t_primary_site = 400;
		wxString t_value = "C504";
		wxString t_temp;
		for (int t_jdx = 0; t_jdx < (int)t_patient->FTumorItems.size(); t_jdx++)
		{
			TDataItem* t_pat_item = t_patient->FindTumorItem(t_jdx, t_primary_site);
			if (t_pat_item)
			{
				if (t_pat_item->ModifiedValue.empty())
				{
					if (t_pat_item->ModifiedValue.compare(t_value) == 0)
					{
						t_temp.Printf("Patient #%d", t_patient->PatientOrdinal);
						FPatientsListBox->Append(t_temp, (wxClientData*)t_patient);
						if (!t_first_patient)
							t_first_patient = t_patient;

					}
					else if (t_pat_item->OriginalValue.compare(t_value) == 0)
					{
						t_temp.Printf("Patient #%d", t_patient->PatientOrdinal);
						FPatientsListBox->Append(t_temp, (wxClientData*)t_patient);
						if (!t_first_patient)
							t_first_patient = t_patient;

					}
					// FIX ME: If t_patient->IsModified, set color and italics
					if (t_patient->IsModified)
					{
						int t_kdx = FPatientsListBox->GetCount() -1;
						if (t_kdx >= 0)
						{
							FPatientsListBox->GetItem(t_kdx)->SetTextColour(*wxRED);
							FPatientsListBox->GetItem(t_kdx)->SetFont(wxFont().Italic());
						}
					}
				}
			}
		}

		itx++;
	}
	// Update the values in the data sizers
	FPatientsListBox->SetSelection(0);
	FPatientsListBox->EnsureVisible(0);

	if (t_first_patient)
	{
		UpdatePatientControls(t_first_patient, FShowAllPanels);
		UpdateTumorControls(t_first_patient, 1, FShowAllPanels);
	}

	event.Skip();
} // OnSetFilter

void TXMLDataForm::OnClearFilter(wxCommandEvent& event)
{
	FIsFiltered = false;
	FPatientsListBox->Clear();

	TPatient* t_first_patient = NULL;

	wxString t_temp;
	std::vector<TPatient*>::iterator itx = FPatients.begin();
	while (itx != FPatients.end())
	{
		TPatient* t_patient = (*itx);
		t_temp.Printf("Patient #%d", t_patient->PatientOrdinal);
		FPatientsListBox->Append(t_temp, (wxClientData*)t_patient);

		if (!t_first_patient)
			t_first_patient = t_patient;

		// FIX ME: If t_patient->IsModified, set color and italics
		if (t_patient->IsModified)
		{
			int t_kdx = FPatientsListBox->GetCount() - 1;
			if (t_kdx >= 0)
			{
				FPatientsListBox->GetItem(t_kdx)->SetTextColour(*wxRED);
				FPatientsListBox->GetItem(t_kdx)->SetFont(wxFont().Italic());
			}
		}

		itx++;
	}
	
	// Update the values in the data sizers
	FPatientsListBox->SetSelection(0);
	FPatientsListBox->EnsureVisible(0);

	if (t_first_patient)
	{
		UpdatePatientControls(t_first_patient, FShowAllPanels);
		UpdateTumorControls(t_first_patient, 1, FShowAllPanels);
	}

	event.Skip();
} // OnClearFilter

void TXMLDataForm::OnSave(wxCommandEvent& event)
{
	// Write data back to file of original name
	event.Skip();
}

void TXMLDataForm::OnSaveAs(wxCommandEvent& event)
{
	// Run a Save-as dialog and write data to new file name
	event.Skip();
}

void TXMLDataForm::OnRunEDITS(wxCommandEvent& event)
{
	// Launch Run EDITS process... dialog to obtain user preferences,
	// create data buffers out of filtered data in tree view
	event.Skip();
}

void TXMLDataForm::OnSelListBox(wxCommandEvent& event)
{
	try
	{
		TPatient* t_patient = (TPatient*)event.GetClientObject();
		if (t_patient)
		{
			UpdatePatientControls(t_patient, FShowAllPanels);
			UpdateTumorControls(t_patient, 1, FShowAllPanels);
		}
	}
	catch (std::runtime_error& e)
	{
		wxLogMessage(e.what());
	}

	event.Skip();
} // OnSelListBox

void TXMLDataForm::CreateDisplayPanels()
{
	std::sort(FPatientDataItemDefs.begin(), FPatientDataItemDefs.end(),
		[](TItemDef* const& a, TItemDef* const& b) { return a->NaaccrId < b->NaaccrId; });

	std::sort(FTumorDataItemDefs.begin(), FTumorDataItemDefs.end(),
		[](TItemDef* const& a, TItemDef* const& b) { return a->NaaccrId < b->NaaccrId; });

	std::sort(FNaaccrDataItemDefs.begin(), FNaaccrDataItemDefs.end(),
		[](TItemDef* const& a, TItemDef* const& b) { return a->NaaccrId < b->NaaccrId; });

	int t_max = FPatientDataItemDefs.size() + FTumorDataItemDefs.size() + FNaaccrDataItemDefs.size(),
	    t_iteration = 0;

	wxBusyInfo dialog("Please wait while edit controls are constructed", this);

	std::vector<TItemDef*>::iterator pit = FPatientDataItemDefs.begin();
	while (pit != FPatientDataItemDefs.end())
	{
		TItemDef* t_item = (*pit);
		TDisplayDataSizer *t_display = new TDisplayDataSizer(FPatientPage, wxVERTICAL, t_item, this);
		FPatientSizer->Add(t_display, wxEXPAND | wxALL);

		pit++;
	}

	std::vector<TItemDef*>::iterator tit = FTumorDataItemDefs.begin();
	while (tit != FTumorDataItemDefs.end())
	{
		TItemDef* t_item = (*tit);
		TDisplayDataSizer *t_display = new TDisplayDataSizer(FTumorPage, wxVERTICAL, t_item, this);
		FTumorSizer->Add(t_display, wxEXPAND | wxALL);

		tit++;
	}

	std::vector<TItemDef*>::iterator nit = FNaaccrDataItemDefs.begin();
	while (nit != FNaaccrDataItemDefs.end())
	{
		TItemDef* t_item = (*nit);
		TDisplayDataSizer *t_display = new TDisplayDataSizer(FNaaccrDataPage, wxVERTICAL, t_item, this);
		FNaaccrDataSizer->Add(t_display, wxEXPAND | wxALL);

		nit++;
	}
} // CreateDisplayPanels

void TXMLDataForm::UpdatePatientControls(TPatient* patient, bool show_all /* == false */)
{
	size_t t_howmany = FPatientSizer->GetItemCount();
	for (size_t t_idx = 0; t_idx < t_howmany; t_idx++)
	{
		// BIG thanks to doublemax on wxWidgets discussion forum for explaining that
		// retrieving a sizer from within a sizer requires calling wxSizerItem::GetSizer...
		// that is, you cannot directly cast from wxSizer::GetItem(t_idx).
		wxSizerItem* szitem = FPatientSizer->GetItem(t_idx);
		TDisplayDataSizer* t_display = static_cast<TDisplayDataSizer*>(szitem->GetSizer());
		if (t_display)
		{
			TItemDef* t_item_def = t_display->GetItemDef();

			if (t_item_def)
			{
				TDataItem* t_pat_item = patient->FindPatientItem(t_item_def->FieldNumber);

				if (t_pat_item)
				{
					if (t_pat_item->ModifiedValue.empty())
					{
						t_display->SetValueText(t_pat_item->OriginalValue);
						t_display->SetValueTextForegroundColour(true);
					}
					else
					{
						t_display->SetValueText(t_pat_item->ModifiedValue);
						t_display->SetValueTextForegroundColour(false);
					}
					t_display->Show(true);
				}
				else
				{
					t_display->SetValueText("");
					t_display->Show(show_all);
				}
			}
		}
	}

	FPatientPage->Layout();

} // UpdatePatientControls

void TXMLDataForm::UpdateTumorControls(TPatient* patient, int tumor_ordinal, bool show_all /* == false */)
{
	if (tumor_ordinal > 0 &&
		tumor_ordinal <= (int)patient->FTumorItems.size())
	{
		FCurrentTumorOrdinal = tumor_ordinal; // the spin button handler needs to know

		FTumorSpinButton->SetMax((int)patient->FTumorItems.size());
		FTumorSpinButton->SetMin(1);
		
		if (patient->FTumorItems.size() > 1)
		{
			wxString t_temp;
			t_temp.Printf("Tumor #%d of %d", tumor_ordinal, patient->FTumorItems.size());
			FNotebook->SetPageText(1, t_temp);

			FSpinLabel->Enable(true);
			FTumorSpinButton->Enable(true);
		}
		else
		{
			FNotebook->SetPageText(1, "Tumor");

			FSpinLabel->Enable(false);
			FTumorSpinButton->Enable(false);
		}

		size_t t_howmany = FTumorSizer->GetItemCount();

		for (size_t t_idx = 0; t_idx < t_howmany; t_idx++)
		{
			wxSizerItem* szitem = FTumorSizer->GetItem(t_idx);
			TDisplayDataSizer* t_display = static_cast<TDisplayDataSizer*>(szitem->GetSizer());
			if (t_display)
			{
				TItemDef* t_item_def = t_display->GetItemDef();

				if (t_item_def)
				{
					size_t t_jdx = tumor_ordinal -1;

					TDataItem* t_pat_item = patient->FindTumorItem(t_jdx, t_item_def->FieldNumber);
					if (t_pat_item)
					{
						if (t_pat_item->ModifiedValue.empty())
						{
							t_display->SetValueText(t_pat_item->OriginalValue);
							t_display->SetValueTextForegroundColour(true);
						}
						else
						{
							t_display->SetValueText(t_pat_item->ModifiedValue);
							t_display->SetValueTextForegroundColour(false);
						}
						t_display->Show(true);
					}
					else
					{
						t_display->SetValueText("");
						t_display->Show(show_all);
					}
				}
			}
		} // for (t_idx...)
	}

	FTumorPage->Layout();
} // UpdateTumorControls

void TXMLDataForm::UpdateNaaccrDataControls(bool show_all /* == false */)
{
	// This gets called only once; the NaaccrData page is always available,
	// but does not change as Patient and Tumor data changes.
	size_t t_howmany = FNaaccrDataSizer->GetItemCount();
	for (size_t t_idx = 0; t_idx < t_howmany; t_idx++)
	{
		wxSizerItem* szitem = FNaaccrDataSizer->GetItem(t_idx);
		TDisplayDataSizer* t_display = static_cast<TDisplayDataSizer*>(szitem->GetSizer());
		if (t_display)
		{
			TItemDef* t_item_def = t_display->GetItemDef();

			if (t_item_def)
			{
				TDataItem* t_data_item = FindNaaccrDataItem(t_item_def->FieldNumber);

				if (t_data_item)
				{
					if (t_data_item->ModifiedValue.empty())
						t_display->SetValueText(t_data_item->OriginalValue);
					else
						t_display->SetValueText(t_data_item->ModifiedValue);

					t_display->Show(true);
				}
				else
				{
					t_display->SetValueText("");
					t_display->Show(show_all);
				}
			}
		}
	}

	FNaaccrDataPage->Layout();

} // UpdateNaaccrDataControls

TDataItem* TXMLDataForm::FindNaaccrDataItem(int FieldNumber)
{
	TDataItem* t_data_item = NULL;
	int t_low = 0,
		t_high = FNaaccrDataItems.size() - 1,
		t_mid;

	while (t_low <= t_high)
	{
		t_mid = (t_low + t_high) / 2;
		if (FNaaccrDataItems[t_mid]->ItemDef->FieldNumber == FieldNumber)
			return FNaaccrDataItems[t_mid];
		else if (FNaaccrDataItems[t_mid]->ItemDef->FieldNumber > FieldNumber)
			t_high = t_mid - 1;
		else
			t_low = t_mid + 1;
	}

	return t_data_item;
} // FindNaaccrDataItem

wxPanel* TXMLDataForm::CreateDataControlPanel(TXMLDataForm *parent)
{
	wxPanel* t_panel = new wxPanel(parent);

	wxString t_options[] = { wxT("Show only those items with data values"),
		wxT("Show all items, including those with blank values") };
	
	FDataRadioBox = new wxRadioBox(t_panel, wxID_ANY, "How do you want to display data?",
		wxDefaultPosition, wxDefaultSize, 2, t_options, 2, wxRA_SPECIFY_ROWS);

	FDataRadioBox->Bind(wxEVT_RADIOBOX, &TXMLDataForm::OnDataRadioBox, this);

	wxBoxSizer *t_sizer = new wxBoxSizer(wxVERTICAL);

	t_sizer->Add(FDataRadioBox, 2, wxEXPAND);

	wxPanel *t_spin_panel = new wxPanel(t_panel);
	wxBoxSizer *t_spin_sizer = new wxBoxSizer(wxHORIZONTAL);

	FSpinLabel = new wxStaticText(t_spin_panel, wxID_ANY, "Peruse the multiple tumors",
		wxDefaultPosition, wxDefaultSize, 0L, "SpinLabel");
	t_spin_sizer->Add(FSpinLabel);
	t_spin_sizer->AddSpacer(8);
	
	FTumorSpinButton = 
		new wxSpinButton(t_spin_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize,
			wxSP_HORIZONTAL | wxSP_ARROW_KEYS);
	
	//t_spin_sizer->Add(FTumorSpinButton, wxSizerFlags().Centre());
	t_sizer->AddSpacer(8);
	t_spin_sizer->Add(FTumorSpinButton, wxSizerFlags().Align(wxALIGN_TOP));
	FTumorSpinButton->Bind(wxEVT_SPIN, &TXMLDataForm::OnSpinTumor, this);

	t_spin_panel->SetSizerAndFit(t_spin_sizer);
	t_sizer->Add(t_spin_panel, 1, wxEXPAND);

	
	t_panel->SetSizer(t_sizer);

	return t_panel;
} // CreateDataControlPanel

void TXMLDataForm::OnDataRadioBox(wxCommandEvent& event)
{
	int t_event_sel = event.GetSelection();

	TPatient* t_patient = (TPatient*)FPatientsListBox->GetClientObject(FPatientsListBox->GetSelection());
	if (t_patient)
	{
		FShowAllPanels = (t_event_sel == 1);

		UpdatePatientControls(t_patient, FShowAllPanels);
		UpdateTumorControls(t_patient, 1, FShowAllPanels);
		FTumorSpinButton->SetValue(1);

		UpdateNaaccrDataControls(FShowAllPanels);
	}

	event.Skip();

} // OnDataRadioBox

void TXMLDataForm::OnSpinTumor(wxCommandEvent& event)
{
	int t_value = FTumorSpinButton->GetValue();

	TPatient* t_patient = (TPatient*)FPatientsListBox->GetClientObject(FPatientsListBox->GetSelection());
	if (t_patient)
	{
		FCurrentTumorOrdinal = t_value;
		UpdateTumorControls(t_patient, FCurrentTumorOrdinal, FShowAllPanels);
	}

	event.Skip();
} // OnSpinTumor

bool TXMLDataForm::RestoreOriginalValue(TItemDef *item_def, wxString& value)
{
	// Called from TDisplayDataSizer pop-up menu item "Restore Original Value"
	bool t_is_changed = false;
	TPatient* t_patient = (TPatient*)FPatientsListBox->GetClientObject(FPatientsListBox->GetSelection());
	if (t_patient)
	{
		TDataItem* t_data_item = t_patient->FindPatientItem(item_def->FieldNumber);

		if (!t_data_item)
		{
			t_data_item = t_patient->FindTumorItem(FCurrentTumorOrdinal - 1, item_def->FieldNumber);
		}

		if (!t_data_item)
		{
			t_data_item = FindNaaccrDataItem(item_def->FieldNumber);
		}

		if (t_data_item)
		{
			if (t_data_item->OriginalValue.compare(value) != 0)
			{
				// the value was modified
				t_data_item->ModifiedValue.assign("");
				value.assign(t_data_item->OriginalValue);
				t_is_changed = true;

				// The patient item in the list box does not get updated 
				// for items on the NaaccrData page, so nothing to undo here.
				if (item_def->ParentXMLElement.compare("NaaccrData") != 0)
				{
					TDataItem* t_found_modified = t_patient->FindFirstModifiedDataItem();
					if (!t_found_modified)
					{
						t_patient->IsModified = false;
						// undo the display of the patient item
						int t_idx = FPatientsListBox->GetSelection();
						FPatientsListBox->GetItem(t_idx)->SetTextColour(*wxBLACK);
						FPatientsListBox->GetItem(t_idx)->SetFont(wxFont());

						FPatientsListBox->RefreshItem(t_idx);

					}
				}
			}
		}
	}

	return t_is_changed;
} // RestoreOriginalValue

void TXMLDataForm::MoveSizerToTop(TDisplayDataSizer *display_sizer)
{
	// We need to know which sizer contains the display_sizer
	wxBoxSizer *t_parent_sizer = NULL;
	TItemDef *t_item = display_sizer->GetItemDef();
	if (t_item)
	{
		if (t_item->ParentXMLElement.compare("Patient") == 0)
			t_parent_sizer = FPatientSizer;
		else if (t_item->ParentXMLElement.compare("Tumor") == 0)
			t_parent_sizer = FTumorSizer;

		if (t_parent_sizer->Detach(display_sizer))
		{
			//t_parent_sizer->Insert(0, display_sizer, wxEXPAND | wxALL);
			t_parent_sizer->Prepend(display_sizer, wxEXPAND | wxALL);
			t_parent_sizer->Layout();

			// We have to change the z-order, and that takes locating the
			// MyTextCtrl on the next TDisplayDataSizer object
			if (t_parent_sizer->GetItemCount() > 1)
			{
				//TDisplayDataSizer *next_sizer = (TDisplayDataSizer*) t_parent_sizer->GetItem(1);
				// Violation: MoveBefore/AfterInTabOrder works only on sibling controls
				//display_sizer->GetMyTextCtrl()->MoveBeforeInTabOrder(next_sizer->GetMyTextCtrl());
				;
			}
		}
	}

} // MoveSizerToTop()

void TXMLDataForm::UpdateOnExitTextCtrl(wxString current_value, TItemDef *item_def, bool& is_changed)
{
	TPatient* t_patient = (TPatient*)FPatientsListBox->GetClientObject(FPatientsListBox->GetSelection());
	if (t_patient)
	{
		TDataItem* t_data_item = t_patient->FindPatientItem(item_def->FieldNumber);

		if (!t_data_item)
		{
			t_data_item = t_patient->FindTumorItem(FCurrentTumorOrdinal-1, item_def->FieldNumber);
		}

		if (!t_data_item)
		{
			t_data_item = FindNaaccrDataItem(item_def->FieldNumber);
		}

		if (t_data_item)
		{
			if (t_data_item->ModifiedValue.empty())
			{
				if (t_data_item->OriginalValue.compare(current_value) != 0)
				{
					is_changed = true;
					t_data_item->ModifiedValue.assign(current_value);

					// Note that this patient is now "modified"
					t_patient->IsModified = true;
					// so paint it red in the list box
					int t_idx = FPatientsListBox->GetSelection();
					FPatientsListBox->GetItem(t_idx)->SetTextColour(*wxRED);
					FPatientsListBox->GetItem(t_idx)->SetFont(wxFont().Italic());

					//FPatientsListBox->Update();
					FPatientsListBox->RefreshItem(t_idx);

					// And for that matter... this form is modified, too
					this->FIsModified = true;
				}
			}
		}
	}
} // UpdateOnExitTextCtrl

Kathleen

ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 3464
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: How to change z-order of wxTextCtrl?

Post by ONEEYEMAN » Fri Mar 15, 2019 8:22 pm

Hi,
I'm not sure you understand the purpose of the sizer.
Sizer is not a window. It does not have anything to display. So, why do you need to have extra class to represent sizer?

Now for you particular problem:
You have all of you text controls to be obejcts of the same class, right?

So when you try to re-arrange them why not re-create them as appropriate to show them as re-arranged?

Let's say you have following:

Code: Select all

wxTextCtrl *text1 = new wxTextCtrl();
text1->SetOptions( "Control1" );
wxTextCtrl *text2 = new wxTextCtrl();
text2->SetOptions( "Control2" );
wxTextCtrl *text3 = new wxTextCtrl();
text3->SetOptions( "Control3" );
Now when you want to re-arrange you do following:

Code: Select all

text1->SetOptions( "Control2" );
text2->SetOptions( "Control1" );
text3->SetOptions( "Control3" );
Why do you need to move them up and down?

But even in this case - the extra class for the sizer is obsolete and it won't be needed. It is not a window.
You were probably thinking about creating a wxPanel-derived class.

Thank you.

kathbeau
Knows some wx things
Knows some wx things
Posts: 48
Joined: Fri Nov 28, 2014 6:30 pm

Re: How to change z-order of wxTextCtrl?

Post by kathbeau » Fri Mar 15, 2019 9:01 pm

I'm not sure you understand the purpose of the sizer.
Sizer is not a window. It does not have anything to display. So, why do you need to have extra class to represent sizer?
I do understand the concept, but I'm still getting the hang of how all the pieces fit together. FWIW, despite a similar warning from doublemax a week or two ago, everything has been working the way I want it to, so I just kept moving along.
Why do you need to move them up and down?
It's part of the specification for the project. The parsed data file contains thousands of patient records, each containing varying numbers of data points. Based on the dictionary that accompanies the data file, there are upwards of 800 distinct data points possible. The initial creation order of these items sorts them on a value that is meaningful to users in this industry, but analysts may at any given time be interested in reviewing particular data points. Rather than have to scroll down the page for each Patient instance, the user can move the panel(s) with the data point(s) to the top of the list.
But even in this case - the extra class for the sizer is obsolete and it won't be needed. It is not a window.
You were probably thinking about creating a wxPanel-derived class.
So it would seem. I'm reworking the code now. So far, I can build the data points panels and display them, but some of the other aspects of my custom class are in a highly explosive state of broken.

Thanks for your help with this.
Kathleen

ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 3464
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: How to change z-order of wxTextCtrl?

Post by ONEEYEMAN » Fri Mar 15, 2019 9:46 pm

Hi,
OK, now I understand what you are trying to achieve.

Take a look at wxGrid.
It supports almost everything - scrolling, multiple data display and even sorting.
You can read you data in some kind of a vector, then populate them in wxGrid. Then when you want to sort them - all you need to do is to re-sort and then redisplay the data.
It will eve show you the sorted column if you use native header (as you should).

On top of that this is exactly the control you are trying to re-create by hand (or re-invent the wheel) ;-)

The cell can even display display native controls (such as button).

And you can even customize the control with the wxConfig options to which column to display.

Check the grid sample and ask more questions if you have any.

But as it stands right now - you are reinventing the wheel.

Thank you.

kathbeau
Knows some wx things
Knows some wx things
Posts: 48
Joined: Fri Nov 28, 2014 6:30 pm

Re: How to change z-order of wxTextCtrl?

Post by kathbeau » Fri Mar 15, 2019 9:47 pm

Okay, so I have changed the custom sizer to a custom panel: class TDisplayDataPanel : public wxPanel

And I place each instance in the same wxBoxSizer I was using when the custom object was a sizer.

Now everything is working exactly as I had it working before... good!

And I still cannot get the tab order to change using MoveBeforeInTabOrder:

Code: Select all

void TXMLDataForm::MoveSizerToTop(TDisplayDataPanel *display_panel)
{
	// We need to know which sizer contains the display_panel
	wxBoxSizer *t_parent_sizer = NULL;
	TItemDef *t_item = display_panel->GetItemDef();
	if (t_item)
	{
		if (t_item->ParentXMLElement.compare("Patient") == 0)
			t_parent_sizer = FPatientSizer;
		else if (t_item->ParentXMLElement.compare("Tumor") == 0)
			t_parent_sizer = FTumorSizer;

		if (t_parent_sizer && t_parent_sizer->Detach(display_panel))
		{
			t_parent_sizer->Prepend(display_panel, wxEXPAND | wxALL);
			t_parent_sizer->Layout();

			// Change the tab order of the panels
			if (t_parent_sizer->GetItemCount() > 1)
			{
				TDisplayDataPanel *next_panel = (TDisplayDataPanel*) t_parent_sizer->GetItem(1);
				
				// Violation: MoveBefore/AfterInTabOrder works only on sibling controls
				display_panel->MoveBeforeInTabOrder(next_panel);
			}
		}
	}
} // MoveSizerToTop()
So while this change improves the code, I still can't solve the problem I have with tabbing through the panels.

Thanks for your suggestions... and please keep them coming.

Kathleen

ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 3464
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: How to change z-order of wxTextCtrl?

Post by ONEEYEMAN » Fri Mar 15, 2019 9:54 pm

Hi,
As I said - take a look at wxGrid and the grid sample.

I think this is what you are looking for.

You will not need any extra code - just plain wxGrid which will be populated from the data you read from the file.

Thank you.

kathbeau
Knows some wx things
Knows some wx things
Posts: 48
Joined: Fri Nov 28, 2014 6:30 pm

Re: How to change z-order of wxTextCtrl? SOLVED

Post by kathbeau » Sat Mar 16, 2019 1:02 pm

Here was my two-fold problem:
1. MoveBefore/AfterInTabOrder works as a swap mechanism; I was attempting to perform a jump.
2. The method I was using to identify sibling panels wasn't right... note the use of display_panel->GetPrevSibling.

But now I have everything working as I want it to.

Code: Select all

void TXMLDataForm::MoveSizerToTop(TDisplayDataPanel *display_panel)
{
	// We need to know which sizer contains the display_panel
	wxBoxSizer *t_parent_sizer = NULL;
	TItemDef *t_item = display_panel->GetItemDef();
	if (t_item)
	{
		if (t_item->ParentXMLElement.compare("Patient") == 0)
			t_parent_sizer = FPatientSizer;
		else if (t_item->ParentXMLElement.compare("Tumor") == 0)
			t_parent_sizer = FTumorSizer;

		if (t_parent_sizer)
		{
			// Fix the tab order of the panels BEFORE actually moving it.
			// Note the use of GetPrevSibling()
			TDisplayDataPanel *prev_panel = (TDisplayDataPanel*)display_panel->GetPrevSibling();
			while (prev_panel)
			{
				display_panel->MoveBeforeInTabOrder(prev_panel);
				prev_panel = (TDisplayDataPanel*)display_panel->GetPrevSibling();
			}

			if (t_parent_sizer->Detach(display_panel))
			{
				//t_parent_sizer->Insert(0, display_panel, wxEXPAND | wxALL);
				t_parent_sizer->Prepend(display_panel, wxEXPAND | wxALL);
				t_parent_sizer->Layout();
			}
		}
	}
} // MoveSizerToTop()

Kathleen


Post Reply