wxTreeListCtrl/wxDataViewCtrl hot-tracking row Topic is solved

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.
User avatar
Kerber
Earned a small fee
Earned a small fee
Posts: 11
Joined: Wed Aug 16, 2017 9:21 pm
Location: Russia

wxTreeListCtrl/wxDataViewCtrl hot-tracking row

Post by Kerber »

Is there a way to implement item hot-tracking for wxTreeListCtrl or generally for wxDataViewCtrl as in wxListCtrl (MSW)? Right now I only achieved selecting row on hover, which is similar, but not exactly what I want.
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxTreeListCtrl/wxDataViewCtrl hot-tracking row

Post by doublemax »

I'm not 100% sure if that's what you mean, but focusing an item is the only alternative to selecting it.
Check wxDataViewCtrl::SetCurrentItem() or the wxLIST_STATE_FOCUSED state for wxListCtrl.
Use the source, Luke!
User avatar
Kerber
Earned a small fee
Earned a small fee
Posts: 11
Joined: Wed Aug 16, 2017 9:21 pm
Location: Russia

Re: wxTreeListCtrl/wxDataViewCtrl hot-tracking row

Post by Kerber »

I want something like wxListCtrl::SetItemState(row, wxLIST_STATE_FOCUSED), but for wxDataViewCtrl. Something that works like setting TVS_TRACKSELECT style for Win32 TreeView control. WxDataViewCtrl can only select item, but not Hot-track it.

Maybe this picture better describe my goal.
Image
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxTreeListCtrl/wxDataViewCtrl hot-tracking row

Post by doublemax »

wxDataViewCtrl::SetCurrentItem() should work then.

It doesn't track the item automatically, you'll have to catch the mouse motion events in the control, find the item under the mouse with wxDataViewCtrl::HitTest() and then call wxDataViewCtrl::SetCurrentItem().
Use the source, Luke!
User avatar
Kerber
Earned a small fee
Earned a small fee
Posts: 11
Joined: Wed Aug 16, 2017 9:21 pm
Location: Russia

Re: wxTreeListCtrl/wxDataViewCtrl hot-tracking row

Post by Kerber »

I'am alredy doing this. But in this case most of the time I don't receive wxEVT_TREELIST_SELECTION_CHANGED nor wxEVT_DATAVIEW_SELECTION_CHANGED event, and I don't know why.

Code: Select all

void wxLTreeList::OnItemHover(wxPoint tMousePos)
{
	wxDataViewItem tItem;
	wxDataViewColumn* pColumn = NULL;
	DataView->HitTest(tMousePos, tItem, pColumn);
	if (tItem.IsOk())
	{
		DataView->SetCurrentItem(tItem);
	}
}
WXLRESULT wxLTreeList::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
{
	if (nMsg == WM_SETCURSOR && HIWORD(lParam) == WM_MOUSEMOVE)
	{
		wxPoint tPos = ScreenToClient(wxGetMousePosition());
		tPos.y -= GetHeaderRect().GetHeight();
		OnItemHover(tPos);
	}
	return wxTreeListCtrl::MSWWindowProc(nMsg, wParam, lParam);
}

wxLTreeList::wxLTreeList(wxWindow* 	parent,
						 wxWindowID id,
						 const wxPoint& pos,
						 const wxSize& size,
						 long style,
						 const wxString& name 
)
{
	Create(parent, id, pos, size, style, name);
	DataView = GetDataView();
	DataView->SetWindowStyle(DataView->GetWindowStyle()|wxDV_VERT_RULES);
	InitHeaderWnd(); // Get HWND of header
	
	Bind(wxEVT_TREELIST_SELECTION_CHANGED, &wxLTreeList::OnSelectTL, this);
	Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, &wxLTreeList::OnSelectDV, this);

	Freeze();
	SetIndent();
	SetItemHeight();
	Thaw();
}
Ellan
Experienced Solver
Experienced Solver
Posts: 57
Joined: Mon May 15, 2017 10:11 am

Re: wxTreeListCtrl/wxDataViewCtrl hot-tracking row

Post by Ellan »

Kerber wrote:I'am alredy doing this. But in this case most of the time I don't receive wxEVT_TREELIST_SELECTION_CHANGED nor wxEVT_DATAVIEW_SELECTION_CHANGED event, and I don't know why.

Code: Select all

void wxLTreeList::OnItemHover(wxPoint tMousePos)
{
	wxDataViewItem tItem;
	wxDataViewColumn* pColumn = NULL;
	DataView->HitTest(tMousePos, tItem, pColumn);
	if (tItem.IsOk())
	{
		DataView->SetCurrentItem(tItem);
	}
}
WXLRESULT wxLTreeList::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
{
	if (nMsg == WM_SETCURSOR && HIWORD(lParam) == WM_MOUSEMOVE)
	{
		wxPoint tPos = ScreenToClient(wxGetMousePosition());
		tPos.y -= GetHeaderRect().GetHeight();
		OnItemHover(tPos);
	}
	return wxTreeListCtrl::MSWWindowProc(nMsg, wParam, lParam);
}

wxLTreeList::wxLTreeList(wxWindow* 	parent,
						 wxWindowID id,
						 const wxPoint& pos,
						 const wxSize& size,
						 long style,
						 const wxString& name 
)
{
	Create(parent, id, pos, size, style, name);
	DataView = GetDataView();
	DataView->SetWindowStyle(DataView->GetWindowStyle()|wxDV_VERT_RULES);
	InitHeaderWnd(); // Get HWND of header
	
	Bind(wxEVT_TREELIST_SELECTION_CHANGED, &wxLTreeList::OnSelectTL, this);
	Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, &wxLTreeList::OnSelectDV, this);

	Freeze();
	SetIndent();
	SetItemHeight();
	Thaw();
}
During the use, I also found that setCurrentItem () did not produce the SelectChange event,which occurs only when the mouse clicks
Thanks

Best Regards

Ellan
Ellan
Experienced Solver
Experienced Solver
Posts: 57
Joined: Mon May 15, 2017 10:11 am

Re: wxTreeListCtrl/wxDataViewCtrl hot-tracking row

Post by Ellan »

Kerber wrote:I want something like wxListCtrl::SetItemState(row, wxLIST_STATE_FOCUSED), but for wxDataViewCtrl. Something that works like setting TVS_TRACKSELECT style for Win32 TreeView control. WxDataViewCtrl can only select item, but not Hot-track it.

Maybe this picture better describe my goal.
Image
I'm also using wxDataViewCtrl, is there any way to hide the dotted border of the selected state?
Thanks

Best Regards

Ellan
User avatar
Kerber
Earned a small fee
Earned a small fee
Posts: 11
Joined: Wed Aug 16, 2017 9:21 pm
Location: Russia

Re: wxTreeListCtrl/wxDataViewCtrl hot-tracking row

Post by Kerber »

Ellan wrote:I'm also using wxDataViewCtrl, is there any way to hide the dotted border of the selected state?
This screenshot shows Windows Task Manager, it uses ListView and I don't know how to disable the dotted border. WxDataViewCtrl doesn't have this border.
Ellan
Experienced Solver
Experienced Solver
Posts: 57
Joined: Mon May 15, 2017 10:11 am

Re: wxTreeListCtrl/wxDataViewCtrl hot-tracking row

Post by Ellan »

Kerber wrote:
Ellan wrote:I'm also using wxDataViewCtrl, is there any way to hide the dotted border of the selected state?
This screenshot shows Windows Task Manager, it uses ListView and I don't know how to disable the dotted border. WxDataViewCtrl doesn't have this border.
The dataView does have this dotted border, I want to hide it, but I can't find the method
Attachments
微信截图_20170817094952.png
微信截图_20170817094952.png (4.42 KiB) Viewed 3941 times
Thanks

Best Regards

Ellan
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxTreeListCtrl/wxDataViewCtrl hot-tracking row

Post by doublemax »

During the use, I also found that setCurrentItem () did not produce the SelectChange event,which occurs only when the mouse clicks
Highlighting (= focusing) an item and selecting are two totally different things. setCurrentItem() sets the *focus*.
Use the source, Luke!
AmadeusK525
Experienced Solver
Experienced Solver
Posts: 61
Joined: Wed Aug 19, 2020 12:04 am

Re: wxTreeListCtrl/wxDataViewCtrl hot-tracking row

Post by AmadeusK525 »

Just for closure I'm gonna post the way I do this in my application, if anyone happens to come across this thread. I'm using wxDataViewCtrl. I've only tested it on wxMSW, since the other versions use a native implementation and this one uses a generic one.

Getting it to work is kinda tricky, since I couldn't find out a way to paint over the control. The reason is that wxDVC works in a weird way, it has a pointer to a wxDataViewMainWindow class, which is what you actually see when using it. This means that wxDVC doesn't actually have an OnPaint() event, so it doesn't matter if you override it or catch the event via event table or Bind()/Connect(), so overriding wxDVC with your own class won't work (which makes things really ugly). What you'd actually need to do is call GetMainWindow()->Bind() or Connect(), this way you'd get the right window. But it doesn't matter, since you're painting always happens first and then the control draws itself, meaning you can't paint over it.

The workaround is using the GetAttr() function from your custom wxDataViewModel and handling the wxEVT_MOTION yourself. Basically what you need to do is:

Firstly, add a 'bool m_isHovering' member to the data that your wxDataViewModel handles. Then, add the static member to it: 'wxDataViewItemAttr m_hoverAttr'. When you get the attr from your data node, check if it's hovering. If it is, return m_hoverAttr, if it isn't, return the normal data attr.

Code: Select all

class YourModelNode {
private:
    bool m_isHovering = false;
    wxDataViewItemAttr m_attr;
	
    static wxDataViewItemAttr m_hoverAttr;
	
public:
    YourModelNode() {
        m_attr.SetBackgroundColour(wxColour(230, 230, 230));
	m_attr.SetColour(wxColour(0, 0, 0));
    }
	
    static void InitHoverAttr() {
        m_hoverAttr.SetColour(wxColour(0, 0, 0));
        m_hoverAttr.SetBackgroundColour(wxColour(180, 180, 180));
    }

    wxDataViewItemAttr& GetAttr() {
        if (m_isHovering)
            return m_hoverAttr;
        else
            return m_attr;
    }
	
    void SetHovering(bool is) { m_isHovering = is; }
};

wxDataViewItemAttr YourModelNode::m_hoverAttr{};
Then, in your wxDataViewModel class, just call the normal YourModelNode::GetAttr() function:

Code: Select all

class YourDataModel : public wxDataViewModel {
public:
    YourDataModel() {
        YourModelNode::InitHoverAttr();
    }
	
    bool GetAttr(const wxDataViewItem& item, unsigned int col, wxDataViewItemAttr& attr) const {
        StoryTreeModelNode* node = (StoryTreeModelNode*)item.GetID();
        if (node)
            attr = node->GetAttr();
        else
            return false;

        return true;
    }
};
Next is binding mouse events to the main window (client area) of your wxDVC and implementing them. We keep track of the current item under the mouse and change the m_isHovering member of the node. Then we refresh it. We also have to unhover the previous item. Everything is pretty straightforward. I implemented the left down function just because of scrolling (if you scroll the wxDVC and don't move the mouse, the hover will stay on the same item, it won't update, even if you, without moving the mouse, select another one. This just gets rid of that. You could also apply the same to a right down event.

Code: Select all

class YourHandlerClass : public wxPanel {
private:
    wxDataViewCtrl* m_yourDVC = nullptr;
    wxDataViewItem m_itemUnderMouse{ nullptr };
    
public:
    YourHandlerClass(wxWindow* parent) : wxPanel(parent) {
        m_yourDVC = new wxDataViewCtrl(this, -1);
        
        m_yourDVC->GetMainWindow()->Bind(wxEVT_MOTION, &YourHandlerClass::OnDVCMouseMove, this);
        m_yourDVC->GetMainWindow()->Bind(wxEVT_LEAVE_WINDOW, &YourHandlerClass::OnDVCLeaveWindow, this);
        m_yourDVC->GetMainWindow()->Bind(wxEVT_LEFT_DOWN, &YourHandlerClass::OnDVCLeftClick, this);
    }
    void YourHandlerClass::OnDVCMouseMove(wxMouseEvent& event) {
        wxDataViewItem item;
        wxDataViewColumn* column;
        m_yourDVC->HitTest(event.GetPosition(), item, column);

        wxRect rect;
        auto CalculateRect = [&](wxDataViewItem& item, wxRect& rect) {
            rect = m_yourDVC->GetItemRect(item);
            rect.width += rect.GetLeft();
            rect.SetLeft(0);
        };

        if (item.IsOk()) {
            if (item != m_itemUnderMouse) {
                if (m_itemUnderMouse.IsOk()) {
                    ((YourModelNode*)m_itemUnderMouse.GetID())->SetHovering(false);
                    CalculateRect(m_itemUnderMouse, rect);
                    m_yourDVC->GetMainWindow()->RefreshRect(rect);
                }

                ((YourModelNode*)item.GetID())->SetHovering(true);
                CalculateRect(item, rect);
                m_yourDVC->GetMainWindow()->RefreshRect(rect);
                m_itemUnderMouse = item;
            }
        } else {
            if (m_itemUnderMouse.IsOk()) {
                ((YourModelNode*)m_itemUnderMouse.GetID())->SetHovering(false);
                CalculateRect(m_itemUnderMouse, rect);
                m_yourDVC->GetMainWindow()->RefreshRect(rect);
            }

            m_itemUnderMouse = item;
        }
    }

    void YourHandlerClass::OnDVCLeaveWindow(wxMouseEvent& event) {
        if (m_itemUnderMouse.IsOk()) {
            ((YourModelNode*)m_itemUnderMouse.GetID())->SetHovering(false);
            wxRect rect(m_yourDVC->GetItemRect(m_itemUnderMouse));
            rect.width += rect.GetLeft();
            rect.SetLeft(0);
            m_yourDVC->GetMainWindow()->RefreshRect(rect);
        }
    }

    void OnDVCMouseMove::OnDVCLeftClick(wxMouseEvent& event) {
        OnStoryViewMouseMove(event);
        event.Skip();
    }
};
P.S. This *isn't* a minimal sample, obviously. I just showed you the needed functions for the hover tracker. You need to override a lot more classes in YourDataModel and extend YourModelNode for this to be actually useful.
User avatar
Kerber
Earned a small fee
Earned a small fee
Posts: 11
Joined: Wed Aug 16, 2017 9:21 pm
Location: Russia

Re: wxTreeListCtrl/wxDataViewCtrl hot-tracking row

Post by Kerber »

Uhh, I didn't know that I still have notifications enabled for this thread. The idea above could work well for the wxDataView but I needed proper hot-tracking and so I took the wxDVC code entirely and totally rewrote it to add hot-tracking and other things I needed.
AmadeusK525
Experienced Solver
Experienced Solver
Posts: 61
Joined: Wed Aug 19, 2020 12:04 am

Re: wxTreeListCtrl/wxDataViewCtrl hot-tracking row

Post by AmadeusK525 »

Kerber wrote: Tue Mar 16, 2021 11:29 pm Uhh, I didn't know that I still have notifications enabled for this thread. The idea above could work well for the wxDataView but I needed proper hot-tracking and so I took the wxDVC code entirely and totally rewrote it to add hot-tracking and other things I needed.
That seems like a sound thing to do. Even though wxDVC is a really really great and imo underappreciated control, it was definitely not written with a class derivative design in mind, so there's not many useful things you can do when deriving from it. In this case altering the source code is probably the better idea.
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxTreeListCtrl/wxDataViewCtrl hot-tracking row

Post by doublemax »

Just be aware that there are three different implementations for wxDVC. The generic one, which is used under Windows, and two wrappers for native controls under GTK and OSX.
Use the source, Luke!
User avatar
Kerber
Earned a small fee
Earned a small fee
Posts: 11
Joined: Wed Aug 16, 2017 9:21 pm
Location: Russia

Re: wxTreeListCtrl/wxDataViewCtrl hot-tracking row

Post by Kerber »

I rewrote the generic one. Added a bunch of features, changed data model interface and made it to use Graphics Renderer to draw its content.
Post Reply