Custom sorting with CGridCtrl with multiple columns - sorting

I sort my CGridCtrl like this at the moment:
m_gridAssignHist.SortItems(pfnCellCompareDate, DISCUSS_COL_DATE, TRUE);
It uses a custom sort function:
int CALLBACK CChristianLifeMinistryDiscussionsDlg::pfnCellCompareDate(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
auto* pCell1 = (CGridCellBase*)lParam1;
auto* pCell2 = (CGridCellBase*)lParam2;
if (!pCell1 || !pCell2) return 0;
auto lDate1 = static_cast<long>(pCell1->GetData());
auto lDate2 = static_cast<long>(pCell2->GetData());
if (lDate1 < lDate2)
return -1;
if (lDate1 > lDate2)
return 1;
return 0;
}
In itself it has no problem. It is just that I would like to add a second layer of sorting if possible. At the moment the data is sorted on column DISCUSS_COL_DATE. If lDate1 is the same as lDate2 then I would like it to sort on the DISCUSS_COL_NAME column. But I can't work out how to establish the row that each cell is on in the grid.
It seems there was method called GetCoords in the source code (found on CodeProject) but they don't seem to do anything.

I came up with a simple solution. It occurred to me that I was adding item data like this:
m_gridAssignHist.SetItemData(iRow, DISCUSS_COL_DATE, CInPlaceDT::GetLongDate(kv.second.datMeeting));
I was being daft! I simply changed it to:
m_gridAssignHist.SetItemData(iRow, DISCUSS_COL_DATE, (LPARAM)&kv.second);
Now the item data is a pointer to the item in the underlying list.
I was able to adapt the sorting comparison function as follows:
int CALLBACK CChristianLifeMinistryDiscussionsDlg::pfnCellCompareDate(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
auto* pCell1 = (CGridCellBase*)lParam1;
auto* pCell2 = (CGridCellBase*)lParam2;
if (!pCell1 || !pCell2) return 0;
auto* pData1 = (CChristianLifeMinistryDefines::S_DISCUSSION_HIST_ITEM*)pCell1->GetData();
auto* pData2 = (CChristianLifeMinistryDefines::S_DISCUSSION_HIST_ITEM*)pCell2->GetData();
if (!pData1 || !pData2) return 0;
if (pData1->datMeeting < pData2->datMeeting)
return -1;
if (pData1->datMeeting > pData2->datMeeting)
return 1;
return pData1->strName.CollateNoCase(pData2->strName);
}
Works as expected:

Related

How to get a complete row data from VTS?

I am using VTS tables for passing data from 1 script to other. Now, I want to get data from all the column for particular row and print that.
I tried couple of VTC commands but unfortunately that did not work.
Command I tried:-
rc = lrvtc_query_row(vuser);
lr_output_message("Col1:- %s", lr_eval_string("{Col1}"));
can you please suggest where I got wrong or provide me a ready code to print he Row from the VTS table.
First you have to enable API access by click the "Enable" button from VTS Web Admin page, then here's the sample:
Action()
{
char* vts_ip = "127.0.0.1";
int vts_port = 8888;
char **colNames = NULL;
char **rowData = NULL;
int row_index = 1;
int i;
PVCI2 pvci = vtc_connect(vts_ip, vts_port, VTOPT_KEEP_ALIVE );
vtc_query_row(pvci, row_index, &colNames, &rowData);
for(i=0; colNames && colNames[i]; ++i){
lr_output_message("%s: %s", colNames[i], rowData[i]);
}
vtc_free_list(colNames);
vtc_free_list(rowData);
return 0;
}

Deleting Particular repeated field data from Google protocol buffer

.proto file structure
message repetedMSG
{
required string data = 1;
}
message mainMSG
{
required repetedMSG_id = 1;
repeated repetedMSG rptMSG = 2;
}
I have one mainMSG and in it too many (suppose 10) repetedMSG are present.
Now i want to delete any particular repetedMSG (suppose 5th repetedMSG )from mainMSG. For this i tried 3 ways but none of them worked.
for (int j = 0; j<mainMSG->repetedMSG_size(); j++){
repetedMSG reptMsg = mainMsg->mutable_repetedMSG(j);
if (QString::fromStdString(reptMsg->data).compare("deleteMe") == 0){
*First tried way:-* reptMsg->Clear();
*Second tried Way:-* delete reptMsg;
*Third tried way:-* reptMsg->clear_formula_name();
break;
}
}
I get run-time error when i serialize the mainMSG for writing to a file i.e. when execute this line
mainMSG.SerializeToOstream (std::fstream output("C:/A/test1", std::ios::out | std::ios::trunc | std::ios::binary)) here i get run-time error
You can use RepeatedPtrField::DeleteSubrange() for this. However, be careful about using this in a loop -- people commonly write code like this which is O(n^2):
// BAD CODE! O(n^2)!
for (int i = 0; i < message.foo_size(); i++) {
if (should_filter(message.foo(i))) {
message.mutable_foo()->DeleteSubrange(i, 1);
--i;
}
}
Instead, if you plan to remove multiple elements, do something like this:
// Move all filtered elements to the end of the list.
int keep = 0; // number to keep
for (int i = 0; i < message.foo_size(); i++) {
if (should_filter(message.foo(i))) {
// Skip.
} else {
if (keep < i) {
message.mutable_foo()->SwapElements(i, keep)
}
++keep;
}
}
// Remove the filtered elements.
message.mutable_foo()->DeleteSubrange(keep, message.foo_size() - keep);

Having trouble implementing a linked list in c++

I am trying to implement a simple singly linked list of integers which are to be sorted upon insertion in Visual Studio c++ 2010 express.
The problem is that when I create a new node and call the .getValue() function on it, the correct number is returned, however somehow that is being lost when I try calling getValue() on a node already in the list. The node might not be inserted into the list correctly, however I can't find why that would be the case. Some other value which looks like a reference value or something is displayed instead of the correct value.
I added current to the watch window when debugging but was still unable to see any of my variables other than the give value to be inserted. I am new to visual studio so I'm not sure if I'm missing something there. Here is my code:
#include "Node.h";
#include <iostream>
//namespace Linked{
//The first two constructors would be the first in the linked list.
Node::Node(void){
value = 0;
next = 0;
}
Node::Node(int setValue){
value = setValue;
next = 0;
}
Node::Node(int setValue,Node *nextNode){
value = setValue;
next = nextNode;
}
Node * Node::getNext(){
return next;
}
void Node::setNext(Node newNext){
next = &newNext;
}
int Node::getValue(){
return value;
}
bool Node::isEqual(Node check){
return value==check.getValue()&&next == check.getNext();
}
/*
int main(){
int firstInt, secondInt;
std::cin>>firstInt;
Node first = Node(firstInt);
std::cout<<"Enter second int: ";
std::cin>>secondInt;
Node second = Node(secondInt, &first);
std::cout<<"Second: "<<second.getValue()<<"\nFirst: "<<(*second.getNext()).getValue();
system("pause");
}*/
Here is the linked list:
//LinkedList.cpp
LinkedList::LinkedList(void)
{
head = 0;
size = 0;
}
LinkedList::LinkedList(int value)
{
head = &Node(value);
size = 1;
}
void LinkedList::insert(int value){
if(head == 0){
Node newNode = Node(value);
head = &newNode;
std::cout<<"Adding "<<(*head).getValue()<<" as head.\n";
}else{
std::cout<<"Adding ";
Node current = *head;
int numChecked = 0;
while(size<=numChecked && (((*current.getNext()).getValue())<value)){
current = (*(current.getNext()));
numChecked++;
}
if(current.isEqual(*head)&&current.getValue()<value){
Node newNode = Node(value, &current);
std::cout<<newNode.getValue()<<" before the head: "<<current.getValue()<<"\n";
}else{
Node newNode = Node(value,current.getNext());
current.setNext(newNode);
std::cout<<newNode.getValue()<<" after "<<current.getValue()<<"\n";
}
}
size++;
}
void LinkedList::remove(int){
}
void LinkedList::print(){
Node current = *head;
std::cout<<current.getValue()<<" is the head";
int numPrinted = 0;
while(numPrinted<(size-1)){
std::cout<<(current.getValue())<<", ";
current = (*(current.getNext()));
numPrinted++;
}
}
int main(){
int a[5] = {30,20,25,13,2};
LinkedList myList = LinkedList();
int i;
for(i = 0 ; i<5 ; i++){
myList.insert(a[i]);
}
myList.print();
system("pause");
}
Any guidance would be greatly appreciated!
When you create nodes in insert, you're allocating them off the stack, which means that they'll be lost after the function returns.
Get them off the heap with:
Node * newNode=new Node(value);
When you use:
Node newNode=Node(value);
You're allocating that object on the stack, which means that pointers:
&newNode
to it are only valid until that function returns. If you use heap memory this is no longer an issue, but it does mean that you have to implement a destructor for your list which goes through and deletes each node.

Refactoring many nested ifs or chained if statements

I have an object with large number of similar fields (like more than 10 of them) and I have to assign them values from an array of variable length. The solution would be either a huge nested bunch of ifs based on checking length of array each time and assigning each field
OR
a chain of ifs checking on whether the length is out of bounds and assigning each time after that check.
Both seem to be repetitive. Is there a better solution ?
If you language has switch/case with fallthrough, you could do it like this:
switch(array.length){
case 15: field14 = array[14];
case 14: field13 = array[13];
case 13: field12 = array[12];
// etc.
case 1: field0 = array[0];
case 0: break;
default: throw Exception("array too long!");
}
for (int i = 0; i < fieldCount; i++)
fields[i].value = array[i];
That is to say, maintain an array of fields that corresponds to your array of values.
If your language supports delegates, anonymous functions, that sort of thing, you can use those to clean it up. For example, in C# you could write this:
string[] values = GetValues();
SomeObject result = new SomeObject();
Apply(values, 0, v => result.ID = v);
Apply(values, 1, v => result.FirstName = v);
Apply(values, 2, v => result.LastName = v);
// etc.
The apply method would look like:
void Apply(string[] values, int index, Action<string> action)
{
if (index < values.Length)
action(values[index]);
}
This is obviously language-dependent, but something to think about regardless.
Another very simple option that we might be overlooking is, if you are actually trying to initialize an object from this value array (as opposed to update an existing object), to just accept the default values if the array isn't large enough.
C# example:
void CreateMyObject(object[] values)
{
MyObject o = new MyObject();
o.ID = GetValueOrDefault<int>(values, 0);
o.FirstName = GetValueOrDefault<string>(values, 0);
o.LastName = GetValueOrDefault<string>(values, 0);
// etc.
}
void GetValueOrDefault<T>(object[] values, int index)
{
if (index < values.Length)
return (T)values[index];
return default(T);
}
Sometimes the dumb solution is the smartest choice.
If your fields are declared in the same order of the array's elements, you could use reflection (if available in your language) to set these values. Here is an example of how you could do it in Java:
// obj is your object, values is the array of values
Field[] fields = obj.getClass().getFields();
for (int i = 0; i < fields.length && i < values.length; ++i) {
fields[i].set(obj, values[i]);
}

Jump to listbox item by typing first few characters

I have a list of items (potentially large) from which the user must select one. I'd like to allow the user to type the first few letters of the desired item to jump to the correct place in the list. By default, each keypress jumps to the first item starting with that letter, so you can't type the first several letters. Is there any straightforward way to do this? Any CodeProject or other such example?
I've looked for hours, and found any number of samples for IAutocomplete, but that won't help here because I need to guarantee that the result is in the list.
The only way I can think to do this is to derive from CListBox, capture the keystrokes myself, find the item, run a timer so that new keystrokes after a sufficient pause will start a new search... since I'm not an MFC jock, this is daunting. Any tips much appreciated.
One clarifying note: my ultimate goal is actually to get this keyboard behavior for a ComboBox of DropDownList style (i.e. no edit box). The lack of an edit box rules out most autocomplete code, and the need for ComboBox functionality means I can't use CListCtrl by itself.
After much unnecessary pain, I've discovered that the real correct answer is simply to use LBS_SORT. Simply by specifying this style, the basic vanilla listbox supports the incremental search keyboard shortcut style I wanted. Without LBS_SORT (or CBS_SORT for a combobox), you get the irritating and almost-useless jump-to-first-letter-only behavior. I didn't try LBS_SORT because my list contents were added in sorted order anyway.
So the dozen or so hours of investigating custom controls, etc., all for naught because the Microsoft documentation makes no mention of this important behavioral difference in the description of LBS_SORT!!
Thanks to everyone who contributed.
I've implemented such a functionality in core Win32. Heres the code.
Somewhere in your message loop that processes the list box insert:
switch(message)
{
case WM_CHAR:
if(HandleListBoxKeyStrokes(hwnd, wParam) == FALSE)
return FALSE;
....
Heres the code (propably not fully complete):
/* ======================================================================== */
/* ======================================================================== */
#define RETURNr(a, b) // homegrown asserts
BOOLEAN HandleListBoxKeyStrokes(HWND hwnd, UINT theKey)
{
#define MAXCHARCACHEINTERVALL 600.0 // Max. milisecs time offset to consider as typed 'at once'
static char sgLastChars[255] = {'0'};
static double sgLastCharTime = 0.;
static HWND sgLasthwnd = NULL;
if(GetSecs() - sgLastCharTime > MAXCHARCACHEINTERVALL ||
sgLasthwnd != hwnd)
*sgLastChars = 0;
if(theKey == ' ' && *sgLastChars == 0)
return TRUE;
sgLastCharTime = GetSecs();
sgLasthwnd = hwnd;
AppendChar(sgLastChars, toupper(theKey));
if(strlen(sgLastChars) > 1)
{
LONG l = GetWindowLong(hwnd, GWL_STYLE);
Char255 tx;
GetClassName(hwnd, tx, sizeof(tx));
if( (! stricmp(tx, "Listbox") &&
! (l & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL)) ) ||
(! stricmp(tx, "ComboBox") && // combo Box support
l & CBS_DROPDOWNLIST &&
! (l & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) ) )
{
long Count, l, BestMatch = - 1, BestMatchOff = 0;
long LBcmdSet[] = {LB_GETCOUNT, LB_GETTEXTLEN , LB_GETTEXT};
long CBcmdSet[] = {CB_GETCOUNT, CB_GETLBTEXTLEN, CB_GETLBTEXT};
long *cmdSet = (! stricmp(tx, "ComboBox")) ? CBcmdSet : LBcmdSet;
RETURNr((Count = SendMessage(hwnd, cmdSet[0], 0, 0)) != LB_ERR, 0);
for(int i = 0; i < Count; i++)
{
RETURNr((l = SendMessage(hwnd, cmdSet[1], i, 0)) != LB_ERR, TRUE);
RETURNr( l < sizeof(tx), TRUE);
RETURNr((l = SendMessage(hwnd, cmdSet[2], i, (LPARAM)&tx)) != LB_ERR, TRUE);
strupr(tx);
if(! strncmp(tx, sgLastChars, strlen(sgLastChars)))
{
SelListBoxAndNotify(hwnd, i);
return FALSE;
}
char *p;
if(p = strstr(tx, sgLastChars))
{
int off = p - tx;
if(BestMatch == -1 || off < BestMatchOff)
{
BestMatch = i;
BestMatchOff = off;
}
}
}
// If text not found at start of string see if it matches some part inside the string
if(BestMatch != -1)
SelListBoxAndNotify(hwnd, BestMatch);
// Nothing found - dont process
return FALSE;
}
}
return TRUE;
}
/* ======================================================================== */
/* ======================================================================== */
void SelListBoxAndNotify(HWND hwnd, int index)
{
// i am sorry here - this is some XVT-toolkit specific code.
// it has to be replaced with something similar for native Win32
WINDOW win = xvtwi_hwnd_to_window(hwnd);
WINDOW parent = xvt_vobj_get_parent(win);
xvt_list_set_sel(win, index, 1);
EVENT evt;
memset(&evt, 0, sizeof(evt));
evt.type = E_CONTROL;
evt.v.ctl.id = GetDlgCtrlID(hwnd);
evt.v.ctl.ci.v.lbox.dbl_click = FALSE;
xvt_win_dispatch_event(parent, &evt);
}
/* ======================================================================== */
/* ======================================================================== */
double GetSecs(void)
{
struct timeb timebuffer;
ftime(&timebuffer);
return (double)timebuffer.millitm +
((double)timebuffer.time * 1000.) - // Timezone needed for DbfGetToday
((double)timebuffer.timezone * 60. * 1000.);
}
/* ======================================================================== */
/* ======================================================================== */
char AppendChar(char *tx, char C)
{ int i;
i = strlen(tx);
tx[i ] = C;
tx[i + 1] = 0;
return(C);
}
Can you use a CListView CListCtrl instead? They work like that by default.

Resources