Update MFC's CSliderCtrl - c++

I am working on a C++ MFC project and bumping in the following. I have a CSliderCtrl on my form which I call MFC_scKINECTANGLE. To make it the way I want it the next piece of code is used:
MFC_scKINECTANGLE = (CSliderCtrl * ) GetDlgItem(SC_kinectAngle);
MFC_scKINECTANGLE->SetRangeMax(27);
MFC_scKINECTANGLE->SetRangeMin(-27);
MFC_scKINECTANGLE->SetPos(0);
The problem is that the slider at the start of the program is at the top of the bar whereas it should be in the middle, and when you try to grab it, it suddenly jumps to the correct position and works fine after that. How can I make sure the slider is in the middle of the bar at the start of my program?

According to MSDN CSliderCtrl::SetRangeMax (CSliderCtrl::SetRangeMin is similar):
void SetRangeMax(
int nMax,
BOOL bRedraw = FALSE
);
You need to set bRedraw parameter to TRUE to update slider.
Another (and probably better) variant - force redraw the slider after setup.
But due to bug (or feature?) in MS trackbar implementation you cannot just call CWnd::Invalidate (for deferred redraw) or even CWnd::RedrawWindow (for immediate redraw). This will have no effect.
Fortunately there are several events that force trackbar to repaint, e.g. enabling/disabling the window:
const BOOL isEnabled = MFC_scKINECTANGLE->IsWindowEnabled();
MFC_scKINECTANGLE->EnableWindow(!isEnabled);
MFC_scKINECTANGLE->EnableWindow(isEnabled);
See this discussion for details.

I was setting the range (0 - 100) and position (50) in the dialog's constructor. The slider kept showing up initially at position 0 instead. If I called GetPos() right after SetPos() it was returning 0 instead of 50.
What made it work for me was overriding OnInitDialog() and setting the range & position there instead of in the constructor.
BOOL CVolumeDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
m_VSliderBarNormal.SetRange(0, 100, TRUE);
m_VSliderBarNormal.SetPos(50);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}

Related

MFC's CTabCtrl::HitTest function returns "1" for any tab clicked

Hi (although greeting usually gets deleted),
I'm using the MFC's CTabCtrl control and try to determine which tab was clicked (to drag & drop it later). Should be quite easy I thought - anyway got stuck with the HitTest function which returns "1" for whichever tab is clicked.
As I started the project very recently, it's literaly just a handful of lines. The mentioned HitTest function is used in Tdi.cpp file in CHlavniOkno::CTdi::OnLButtonDown function (full source code at http://nestorovic.hyperlink.cz/cpp_mfc.zip ):
afx_msg void CHlavniOkno::CTdi::OnLButtonDown(UINT flagy,CPoint bod){
if (::DragDetect(m_hWnd,bod)){
TCHITTESTINFO hti={bod};
if (int idZalozky=HitTest(&hti)>=0)
parametryTazeneZalozky=new TParametryTazeneZalozky(this,idZalozky);
}
CTabCtrl::OnLButtonDown(flagy,bod);
}
I definitely must have omitted something tiny, as is almost always the case...
Thanks for your time by having a look at the problem.
Tomas
The statement int idZalozky=HitTest(&hti)>=0 is setting idZalozky to the result of the test HitTest(&hti)>=0. As a boolean test this will always return either 0 or 1.
You probably want:
int idZalozky=HitTest(&hti);
if (idZalozky>=0)
{
...
}

CMFCOutlookBarTabCtrl::SetActiveTab not working

I have added a CMFCOutlookBar control to a dialog. This outlookbar contains some 12 trees.
As per following link https://msdn.microsoft.com/en-us/library/bb983453.aspx
we can set active tab (in my case tree control) of our wish.
but it doesn't seems to work.
as per above link this function returns non zero value on success. Indeed it is returning 1 when i used it to set tree of my choice. but visually it's not changed.
can someone help me?
Problem solved.
CMFCOutlookBarTabCtrl::SetActiveTab() only works after window has been displayed.
I guess this is because CMFCOutlookBar stores it's previous state to registory and reloads on next run. And this overrides changes made by SetActiveTab(), if we use it before displaying of window.
I had the same problem, and you are correct that on load the tab gets set to the last session value - actually it seems to get set several times during the load process - some of them seem to correspond to each time a tab is added, and then the last time it is called seems to be the tab from the previous session.
The solution is to set the value once the window is ready to be shown. This can be done by overriding the OnShowWindow callback on the view which contains the tab bar.
In my case the tab bar is added in a view called MainFrame, which has a member variable CMFCOutlookBarTabCtrl* m_pOutlookBar; which is initialised in the OnCreate callback.
I can then correctly set the tab by overriding OnShowWindow to contain the following:
void MainFrame::OnShowWindow(BOOL bShow, UINT nStatus)
{
CFrameWndEx::OnShowWindow(bShow, nStatus);
if ((m_pOutlookBar != NULL) && bShow) {
//When the tab bar is shown, select the correctview
for (int tabIdx = 0; tabIdx < m_pOutlookBar->GetTabsNum(); tabIdx++) {
CString requiredLabel;
CString thisLabel;
requiredLabel.LoadString(IDS_OF_TAB); //The ID of the tab wanted
m_pOutlookBar->GetTabLabel(tabIdx,thisLabel);
if (requiredLabel.Compare(thisLabel) == 0) {
//If the tab label matches the one required
m_pOutlookBar->SetActiveTab(tabIdx); //set it as the active one.
break; //done.
}
}
}
}

What Uxtheme function I must use to get the default size of the minimize, Maximize and close buttons?

I'm using the DrawThemeBackground function to draw some system elements on a canvas, And I need draw the title buttons of a form, the only part that i missed is how i can get the default sizes of the title buttons. Exist any Uxtheme function to get that info?
Looks like this is more difficult then it sounds.
First there's GetThemeMetric or GetThemeInt. But you'll see a lot of references that these functions return a 0x8007490, some "element not found", when you try to retrieve properties of caption buttons.
Then there's GetThemePartSize. This one seems to work some. That is it works ok for instance for WP_CLOSEBUTTON, but it returns nonsense for instance for WP_MINBUTTON. I would not suggest this function's use anyway since it retrieves the default dimensions of the button. If the user has changed the title size for instance, you won't get correct values. Anyway, it could be called like this:
uses
uxtheme, themes;
...
var
Err: HRESULT;
Size: TSize;
begin
Err := GetThemePartSize(ThemeServices.Theme[teWindow], 0,
WP_CLOSEBUTTON, CBS_NORMAL, nil, TS_TRUE, Size);
I have no idea what the former two functions would return if they worked (the dimensions of buttons for current title bar size or the default title bar size).
The only possible way to get an accurate result seems to be to use the WM_GETTITLEBARINFOEX message. But there's a drawback; it works only for Vista and up. You may need to define the message and the struct it uses depending on the Delphi version you use (D2007 here).
const
CCHILDREN_TITLEBAR = 5;
WM_GETTITLEBARINFOEX = $033F;
type
tagTITLEBARINFOEX = record
cbSize: DWORD;
rcTitleBar: TRect;
rgstate: array[0..CCHILDREN_TITLEBAR] of DWORD;
rgrect: array [0..CCHILDREN_TITLEBAR] of TRect;
end;
TITLEBARINFOEX = tagTITLEBARINFOEX;
TTitleBarInfoEx = tagTITLEBARINFOEX;
PTitleBarInfoEx = ^TTitleBarInfoEx;
...
var
TitleInfo: TTitleBarInfoEx;
begin
SendMessage(Handle, WM_GETTITLEBARINFOEX, 0, NativeInt(#TitleInfo));
Then, you can get the size for the close button from the rect TitleInfo.rgrect[5]. See "TITLEBARINFOEX structure" for details. Notice the values are in screen coordinates.
If you need to support XP and/or below, I suggest you to use the good old GetSystemMetrics(SM_CXSIZE) and GetSystemMetrics(SM_CYSIZE) ("The width of a button in a window caption or title bar, in pixels"). You'd need to workout some approximations depending on if themes are enabled, if aero is enabled etc..
I think SystemParametersInfo with SPI_GETNONCLIENTMETRICS is what you're looking for. I guess the minimize and maximize buttons use NONCLIENTMETRICS.iSmCaptionWidth while close uses iCaptionWidth to determine width.

"Smart" Linked Scrollbar and Edit Controls?

I hope that I can explain my problem well enough for someone to help.
Basically, I have a horizontal scrollbar (ranged 0 to 1000) and an edit control that represents the position of the scrollbar divided by 1000, so that the user can use either the scrollbar to select a range of numbers between 0 and 1 up to a 3 decimal precision (.001, .002, ..., .987, etc.), or enter their own number in the edit box. As they scroll the scrollbar, the number in the edit control changes to reflect the new scroll position. When a new number is entered, the scrollbar sets itself to a new position reflecting the number entered. Meanwhile I also perform some calculations with this number as it changes (through either the scrollbar or the edit control) and display the results in another dialog.
Here is my problem: I'm having trouble deciding which event handlers to use to produce the proper behavior when a user enters a number into the edit control.
I'm using a double value variable called fuelMargin to handle my edit control and a CScrollBar control variable called fuelScroll to handle the scrollbar.
In my HSCROLL event I set the edit control to the scroll position / 1000. No problems there; when the user scrolls the scrollbar the edit box is correctly updated.
As for the edit box, my first attempt was an ONCHANGE event:
void MarginDlg::OnEnChangeFueledit()
{
CEdit* editBox;
editBox = (CEdit*)GetDlgItem(IDC_FUELEDIT);
CString editString;
editBox->GetWindowText(editString);
if (editString.Compare(".") != 0 && editString.Compare("0.") != 0
&& editString.Compare(".0") != 0 && editString.Compare("0.0") != 0
&& editString.Compare(".00") != 0 && editString.Compare("0.00") != 0)
{
UpdateData();
UpdateData(FALSE);
if (fuelMargin > 1)
{
UpdateData();
fuelMargin = 1;
UpdateData(FALSE);
}
if (fuelMargin < 0)
{
UpdateData();
fuelMargin = 0;
UpdateData(FALSE);
}
fuelScroll.SetScrollPos(int(fuelMargin*1000));
}
}
I needed that first if statement in there to keep from doing an UpdateData() when the user is trying to type a number like .5 or .05 or .005. It does produce a few wonky behaviors, though; when the user tries to type something like .56, after the .5 an UpdateData() is performed, the number becomes 0.5, and the cursor is moved to the far left, so if they tried to type .56 they would accidentally end up typing 60.5 -- which goes to 1, since I won't let them enter numbers lower than 0 or higher than 1. If they enter 0.56, however, this behavior is avoided.
For my second attempt, I commented out my ONCHANGE event and put in an ONKILLFOCUS event instead:
void MarginDlg::OnEnKillfocusFueledit()
{
UpdateData();
UpdateData(FALSE);
if (fuelMargin > 1)
{
UpdateData();
fuelMargin = 1;
UpdateData(FALSE);
}
if (fuelMargin < 0)
{
UpdateData();
fuelMargin = 0;
UpdateData(FALSE);
}
fuelScroll.SetScrollPos(int(fuelMargin*1000));
}
So now the user can finish typing their number and all is hunky dory--as long as they click out of the edit box. The scrollbar won't move and results won't be calculated until the box loses focus.
I want the results to be calculated as the numbers in the box are being typed; I want the scrollbar to move as the numbers are being typed. But I don't want typing to be disrupted, i.e. the actual numbers in the box changed or the cursor moved in any way.
Suggestions?
Thanks!
With the first approach, it looks like you're almost there: the only really significant problem is that the repeated calls to UpdateData() mess with the cursor position as the user is typing.
Given that you're trying to have a reasonably complex interaction between the controls, what I'd suggest is not to do validation in the OnChange() at all, so that as the user is typing he can type what he wants (which is how most numeric edit controls work anyway). When the user closes the dialog the controls are on (or clicks a button that uses the data in some way) then validation should be triggered, and a suitable error shown.
Once you're free from validating in OnChange(), you can fix the "cursor moves" problem by simply not calling UpdateData() in OnChange(). Instead, just parse the number from "editString" and, if it's in the valid range, update the scrollbar. That way, the scrollbar updates as the user types, and if they type in an invalid value the scrollbar stays put, and they'll get an error when they move to whatever the next stage is. Something like this (not tested):
void MarginDlg::OnEnChangeFueledit()
{
CString editString;
GetDlgItem(IDC_FUELEDIT)->GetWindowText(editString);
double editValue;
if ((sscanf(editString,"%lf",&editValue) == 1)
{
if (editValue >= 0.0) && (editValue <= 1.0))
fuelScroll.SetScrollPos(int(editValue*1000));
}
}
The only remaining important problem to note is that, if the user types some invalid value, or a number out of the valid range, then the edit control and the scrollbar will be out of sync. The simplest way to deal with that is to just decide that the edit control is the "master" value: that is, when we want to know what the user entered, we always look at the edit control, not the scrollbar, and validate data.
As for your second approach, one possible solution might be to implement a timer message handler: in the timer handler you could say the equivalent of "if the user hasn't typed anything for a second, I'll assume they're done, and parse the number and update the scrollbar". I'm not so keen on that as a solution, though.
I'd suggest watching for the Enter key, and performing UpdateData() then as well as OnKillFocus and OnChange. The more user-friendly the better.
Next, make sure your UpdateData() routine only works in the direction you need:
If ".5" is entered in the edit control, run your UpdateData() routine when the OnChange event is raised, but make sure to update your scrollbar only. Don't worry about updating the edit control to "0.5" until OnKillFocus is raised. Any updates in the reverse direction (to the edit control) will mess with your cursor. If your edit control is somehow bound to this double variable and auto updates when the var changes, consider leaving the double and your edit control seperate from each other until the OnKillFocus event is raised.
The same concept applies in the other direction as well. When the user scrolls, don't mess with the scrollbar. Just update the edit control and leave it at that.
I should add that XAML's data-binding features really help in situations like this, if you know how to use them properly. It's unfortunate for us native-type developers that it's so difficult to implement similar functionality using only event handlers.

Programmatically change combobox

I need to update a combobox with a new value so it changes the reflected text in it. The cleanest way to do this is after the comboboxhas been initialised and with a message.
So I am trying to craft a postmessage to the hwnd that contains the combobox.
So if I want to send a message to it, changing the currently selected item to the nth item, what would the postmessage look like?
I am guessing that it would involve ON_CBN_SELCHANGE, but I can't get it to work right.
You want ComboBox_SetCurSel:
ComboBox_SetCurSel(hWndCombo, n);
or if it's an MFC CComboBox control you can probably do:
m_combo.SetCurSel(2);
I would imagine if you're doing it manually you would also want SendMessage rather than PostMessage. CBN_SELCHANGE is the notification that the control sends back to you when the selection is changed.
Finally, you might want to add the c++ tag to this question.
A concise version:
const int index = 0;
m_comboBox.PostMessage(CBN_SELCHANGE, index);
What might be going wrong is the selection is being changed inside the selection change message handler, which result in another selection change message.
One way to get around this unwanted feedback loop is to add a sentinel to the select change message handler as shown below:
void onSelectChangeHandler(HWND hwnd)
{
static bool fInsideSelectChange = 0;
//-- ignore the change message if this function generated it
if (fInsideSelectChange == 0)
{
//-- turn on the sentinel
fInsideSelectChange = 1;
//-- make the selection changes as required
.....
//-- we are done so turn off the sentinel
fInsideSelectChange = 0;
}
}
if you fx want to change the title - which is the line shown when combobox is closed, then you can do following:
m_ComboBox.DeleteString(0); // first delete previous if any, 0 = visual string
m_ComboBox.AddString(_T("Hello there"));
put this in fx. in OnCloseupCombo - when event close a dropdownbox fires
ON_CBN_CLOSEUP(IDC_COMBO1, OnCloseupCombo)
This change is a new string not a selection of already assigned combobox elements