Visual Studio 2008 (formely code-named ‘Orcas’) has several important updates for VC++ and MFC. Among them the possibility to create syslink controls, command or split buttons and network address controls. In this post I will show how you can work with the syslink control. The control provides a way to embed hypertext links in a window. It is actually a window that renders marked-up text just as hyperlinks in a web browser. Multiple links can be put in a single control, and are accessed by a zero-based index.

Currently it supports the anchor tag (<A>) with the HREF and ID attributes. HREF is used to specify a URL of any protocol (http, ftp, mailto, etc.). On the other hand ID specifies an unique name within the control, associated with an individual link.

The content is available in the toolbar, so you can simply drag and drop syslink controls onto you dialog template.

Syslink controls on dialog

You can specify the text, including the anchor tag from the properties page, or you can use the SetWindowText function to set it at run-time.

GetDlgItem(IDC_SYSLINK1)->SetWindowText(
   L"Visit my web site"
   L" and check my blog.");



You have to handle the NM_CLICK notification, check which link was clicked and take the appropriate action:

BEGIN_MESSAGE_MAP(CMFCDemoDlg, CDialog)
	ON_NOTIFY(NM_CLICK, IDC_SYSLINK1, &CMFCDemoDlg::OnNMClickSyslink1)
END_MESSAGE_MAP()   

void CMFCDemoDlg::OnNMClickSyslink1(NMHDR *pNMHDR, LRESULT *pResult)
{
	PNMLINK pNMLink = (PNMLINK) pNMHDR;   

	if (wcscmp(pNMLink->item.szUrl, WEB_SITE) == 0)
	{
		ShellExecuteW(NULL, L"open", pNMLink->item.szUrl, NULL, NULL, SW_SHOWNORMAL);
	}
	else if(wcscmp(pNMLink->item.szUrl, BLOG_LINK) == 0)
	{
		ShellExecuteW(NULL, L"open", pNMLink->item.szUrl, NULL, NULL, SW_SHOWNORMAL);
	}   

	*pResult = 0;
}

In MFC 9.0 (version that will ship with Visual Studio 2008) class CLinkCtrl is a wrapper over the Windows API for working with the syslink control.

You can associate an instance of CLinkCtrl with a syslink control through the DDX mechanism:

void CMFCDemoDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_SYSLINK2, Link2);
}

In my demo application that you can download from here I used a second syslink with an ID attribute. Each time the link is clicked it increments a counter, which is shown. For that I created two helper functions first, one to generate part of the text and the second to set the text to the link control:

CString CMFCDemoDlg::GetClickText() const
{
	CString str;
	str.Format(L"clicked %d times", Clicks);
	return str;
}   

void CMFCDemoDlg::SetLink2Text()
{
	Link2.SetWindowText(L"Link was " + GetClickText() + L"");
}

When handling the NM_CLICK notification, I checked the szID member of structure LITEM and took the appropriate action:

void CMFCDemoDlg::OnNMClickSyslink2(NMHDR *pNMHDR, LRESULT *pResult)
{
	PNMLINK pNMLink = (PNMLINK) pNMHDR;   

	if (wcscmp(pNMLink->item.szID, L"clicked") == 0)
	{
		Clicks++;
		SetLink2Text();
	}   

	*pResult = 0;
}

The result is shown here:

Syslink controls on dialog

Hopefully the sample code that I put together will help you work with CLinkCtrl and the syslink control in VS 2008.

Hits for this post: 19011 .

Being active on large communities such as CodeGuru or MSDN forums I see every day people asking question in the worst possible manner; questions that simply can’t be answered because they are either very ambiguous or ar lacking any useful information about the problem. Some time ago I decided to make a list os such questions. The purpose of it is to prevent people from doing the same mistakes.

Badly asked questions from CodeGuru.com

Badly asked questions from MSDN forums:

Some of my favorite are:

My advice: DON’T POST LIKE THAT! PEOPLE WON’T BE ABLE TO HELP YOU! Be as precise and detailed as you can.

Hits for this post: 4004 .

Recently I’ve been asked how you can make method find of std::string case insensitive. An obvious solution is to transform the strings to lower case, for instance, and perform the search in lower case. The solution has some inconvenients and a clean and nice solution is defining custom traits that ignore the case. The solution I’m showing bellow is based on an example from the book “The C++ Standard Library – A Tutorial and Reference” by Nicolai M. Josuttis (Addison Wesley, 1999). The original example is available online on the author’s web page: http://www.josuttis.com/libbook/string/icstring1.cpp.html.

What I’m showing below is a modified implementation that allows working both with strings of char and wchar_t.

For the start, a custom char_traits implementation must be provided:

// define a custom traits class that ignores case
template struct ignorecase_traits : public std::char_traits
{
	typedef T		char_type;
	typedef int		int_type;
	typedef std::streamoff	off_type;
	typedef std::streampos	pos_type;
	typedef std::mbstate_t	state_type;

	// Performs the assignment dst = src
	static void assign(char_type& dst, const char_type src)
	{
		dst = src;
	}

	// Assigns the value c to each pointer in the range [dst, dst+n), and returns dst
	static char_type* assign(char_type* dst, size_t n, char_type c)
	{
		return static_cast(std::memset(dst, n, c));
	}

	// return whether c1 and c2 are equal
	static bool eq(const char_type& c1, const char_type& c2)
	{
		return std::toupper(c1)==std::toupper(c2);
	}

	// return whether c1 is less than c2
	static bool lt(const char_type& c1, const char_type& c2)
	{
		return std::toupper(c1) < std::toupper(c2);
	}

	// compare up to n characters of s1 and s2
	static int compare(const char_type* s1, const char_type* s2, std::size_t n)
	{
		for (std::size_t i=0; i < n; ++i)
		{
			if (!eq(s1[i], s2[i]))
			{
				return lt(s1[i], s2[i])?-1:1;
			}
		}
		return 0;
	}

	// search c in s
	static const char_type* find(const char_type* s, std::size_t n, const char_type& c)
	{
		for (std::size_t i=0; i < n; ++i)
		{
			if (eq(s[i], c))
			{
				return &(s[i]);
			}
		}
		return 0;
	}
};



Structure ignorecase_traits is parameterized, so that we will be able to instantiate it both for char and wchar_t. Two typedefs of basic_string with the new ignorechar_traits are necessary:

// define a special type for a string of char
typedef std::basic_string> icstring;

// define a special type for a strings of wchar_t
typedef std::basic_string> icwstring;



To be able to print objects of type icstring or icwstring an overload of operator<< is required:

template 
inline std::basic_ostream& operator << (std::basic_ostream& strm,
    const std::basic_string>& s)
{
	return strm << s.c_str();
}



Having all that implemented, we can use the two types, like the shown below:

int _tmain(int argc, _TCHAR* argv[])
{
	icstring is1("marius");
	icstring is2("marIUS");

	std::cout << std::boolalpha;
	std::cout << is1 << " == " << is2 << " : " << (is1==is2) << std::endl;

	icwstring iws1(L"bancila");
	icwstring iws2(L"banCILA");

	std::wcout << std::boolalpha;
	std::wcout << iws1 << L" == " << iws2 << L" : " << (iws1==iws2) << std::endl;

	if(is2.find("Riu") != std::string::npos)
		std::cout << "Riu found in " << is2 << std::endl;

	std::wstring ws1(L"cil");
	icwstring iws3(ws1.begin(), ws1.end());

	if(iws2.find(iws3) != std::string::npos)
		std::wcout << iws3 << L" found in " << iws2 << std::endl;

	return 0;
}



If you run the code you'll get the following output:

marius == marIUS : true
bancila == banCILA : true
Riu found in marIUS
cil found in banCILA


Hits for this post: 4504 .