I am working on a simple UWP application written in C++/WinRT under Windows 10 that contains two ListView
controls. The goal of this application is to learn how to select an item from one ListView
control, drag it to the other ListView
control, and drop the item so that it is copied from the source ListView
control to the destination ListView
control.
All of the examples I have found thus far use C# with a few using C++/CX rather than C++/WinRT and native C++ however I have managed to slog through to the point where the basic mechanics of selecting an item from the source ListView
works as does the drag and the drop onto the destination ListView
. However when trying to fetch the information from the drop event in order to update the destination ListView
I am getting an exception.
Question: What changes do I need to make so that the selected text in the source ListView
control can be dragged and dropped on the destination ListView
control and the text then be added to the destination ListView
control?
The Output window of Visual Studio 2017 shows the following text which I interpret to be a bad address exception:
Unhandled exception at 0x0259DC3C (Windows.UI.Xaml.dll) in TouchExperiment_01.exe: 0xC000027B: An application-internal exception has occurred (parameters: 0x05F5E3D8, 0x00000005).
Unhandled exception at 0x74ECE61D (combase.dll) in TouchExperiment_01.exe: 0xC0000602: A fail fast exception occurred. Exception handlers will not be invoked and the process will be terminated immediately.
Unhandled exception at 0x74F9D7D9 (combase.dll) in TouchExperiment_01.exe: Stack cookie instrumentation code detected a stack-based buffer overrun.
Unhandled exception at 0x74F9D7D9 (combase.dll) in TouchExperiment_01.exe: Stack cookie instrumentation code detected a stack-based buffer overrun.
The exception is raised when the following line of source code in the function void MainPage::OnListViewDrop()
, which is the last function in the MainPage.cpp source file, is executed:
auto x = e.DataView().GetTextAsync();
Additional Information A: Using the debugger I found that the error message associated with the exception which implies an error in the data provided by the method OnListViewDragItemsStarting()
. The text of the exception error message is:
{m_handle={m_value=0x05550330 L"DataPackage does not contain the specified format. Verify its presence using DataPackageView.Contains or DataPackageView.AvailableFormats." } }
I also found at the site where the exception is first thrown and caught by Visual Studio, stopping the application in base.h
(source from C++/WinRT templates), an error text of 0x8004006a : Invalid clipboard format
indicating that I have lack of agreement on the format of the data that the start of drag creates and the drop of drag is trying to consume.
An overview of the source code
I modified a standard C++/WinRT app template in the area of the MainPage.xml, MainPage.cpp, MainPage.h, and pch.h. I also added the class files for a new class, DataSource, which uses a std::vector<>
to contain some test data. This memory resident data is initialized with some dummy data in the App
constructor:
App::App()
{
InitializeComponent();
DataSource::InitializeDataBase();
Suspending({ this, &App::OnSuspending });
// … other code
First of all I had to add a line to the pch.h file to provide the templates for drag and drop:
#include "winrt/Windows.ApplicationModel.DataTransfer.h" // ADD_TO: need to add to allow use of drag and drop in MainPage.cpp
The XAML source file contains the source for the two ListView
controls as well as a TextBlock
control which displays a full description of the item selected in the source ListView
:
<Page
x:Class="TouchExperiment_01.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TouchExperiment_01"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Width="1130" Margin="0,0,0,0">
<ListView x:Name="myList" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="300" SelectionChanged="OnSelectionChanged"
CanDragItems="True" DragItemsStarting="OnListViewDragItemsStarting" BorderBrush="AliceBlue" BorderThickness="3">
</ListView>
<TextBlock x:Name="myTextBlock" Height="200" Width="200" Text="this is temp text to replace." TextWrapping="WrapWholeWords" Margin="5"/>
<ListView x:Name="myList2" HorizontalAlignment="Right" Height="100" VerticalAlignment="Top" Width="300" SelectionChanged="OnSelectionChanged" AllowDrop="True"
DragOver="OnListViewDragOver" Drop="OnListViewDrop" BorderBrush="DarkGreen" BorderThickness="5">
</ListView>
</StackPanel>
</Page>
The class declaration for DataSource
is simple. The class definition is as follows:
#pragma once
class DataSource
{
public:
DataSource();
~DataSource();
static int InitializeDataBase();
struct DataSourceType
{
std::wstring name;
std::wstring description;
};
static std::vector<DataSourceType> myDataBase;
}
;
and the initialization of the vector
, which is done when the App
constructs when the application starts up is:
int DataSource::InitializeDataBase()
{
myDataBase.clear();
for (int i = 0; i < 50; i++) {
DataSourceType x;
wchar_t buffer[256] = { 0 };
swprintf_s(buffer, 255, L"Name for %d Item", i);
x.name = buffer;
swprintf_s(buffer, 255, L"Description %d. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.", i);
x.description = buffer;
myDataBase.push_back(x);
}
return 0;
}
The MainPage.cpp source code behind the XAML page is:
#include "pch.h"
#include "MainPage.h"
#include "DataSource.h"
using namespace winrt;
using namespace Windows::UI::Xaml;
namespace winrt::TouchExperiment_01::implementation
{
MainPage::MainPage()
{
InitializeComponent();
// load up the source ListView with the name field from out
// in memory database.
auto p = myList().Items();
for (auto a : DataSource::myDataBase) {
p.Append(box_value(a.name));
}
// add a single ListViewItem to the destination ListView so that we
// know where it is.
p = myList2().Items();
p.Append(box_value(L"list"));
}
int32_t MainPage::MyProperty()
{
throw hresult_not_implemented();
}
void MainPage::MyProperty(int32_t /* value */)
{
throw hresult_not_implemented();
}
void MainPage::OnSelectionChanged(Windows::Foundation::IInspectable const & target, Windows::UI::Xaml::RoutedEventArgs const & )
{
// the user has selected a different item in the source ListView so we want to display
// the associated description information for the selected ListViewItem.
winrt::Windows::UI::Xaml::Controls::ListView p = target.try_as<winrt::Windows::UI::Xaml::Controls::ListView>();
if (p) {
int iIndex = p.SelectedIndex();
myTextBlock().Text(DataSource::myDataBase[iIndex].description);
}
}
void MainPage::OnListViewDragItemsStarting(Windows::Foundation::IInspectable const & target, Windows::UI::Xaml::Controls::DragItemsStartingEventArgs const & e)
{
// provide the data that we have in the ListView which the user has selected
// to drag to the other ListView. this is the data that will be copied from
// the source ListView to the destination ListView.
auto p = target.try_as<winrt::Windows::UI::Xaml::Controls::ListView>();
if (p) {
int iIndex = p.SelectedIndex();
e.Items().SetAt(0, box_value(iIndex));
}
}
void MainPage::OnListViewDragOver(Windows::Foundation::IInspectable const & target, Windows::UI::Xaml::DragEventArgs const & e)
{
// indicate that we are Copy of data from one ListView to another rather than one of the other
// operations such as Move. This provides the operation type informative user indicator when the
// user is doing the drag operation.
e.AcceptedOperation(Windows::ApplicationModel::DataTransfer::DataPackageOperation::Copy);
}
void MainPage::OnListViewDrop(Windows::Foundation::IInspectable const & target, Windows::UI::Xaml::DragEventArgs const & e)
{
// update the destination ListView with the data that was dragged from the
// source ListView.
auto p = target.try_as<winrt::Windows::UI::Xaml::Controls::ListView>();
auto x = e.DataView().GetTextAsync(); // ** this line triggers exception on drop.
}
}
A screen shot of the application with an item selected in the source ListView
before a drag is started looks is as follows. The source ListView
control is on the left and the destination ListView
control is on the right.
Addendum: References and documentation
Microsoft Docs - Windows.ApplicationModel.DataTransfer Namespace
Microsoft Docs - DragItemsStartingEventArgs Class which contains a link to this example project that looks to be using C++/CX Drag and drop sample on GitHub which contains Windows-universal-samples/Samples/XamlDragAndDrop/cpp/Scenario1_ListView.xaml.cpp that has a useful example.
The reason for the exception was due to a misuse of the GetTextAsync()
method which is an asynchronous method that requires using threads, tasks, coroutines, or some other concurrency functionality.
I found the example source code Windows-universal-samples/Samples/XamlDragAndDrop/cpp/Scenario1_ListView.xaml.cpp which provided the hint as to what I was doing wrong. See also the article at https://github.com/Microsoft/cppwinrt/blob/master/Docs/Using%20Standard%20C%2B%2B%20types%20with%20C%2B%2B%20WinRT.md
// We need to take a Deferral as we won't be able to confirm the end
// of the operation synchronously
auto def = e->GetDeferral();
create_task(e->DataView->GetTextAsync()).then([def, this, e](String^ s)
{
// Parse the string to add items corresponding to each line
auto wsText = s->Data();
while (wsText) {
auto wsNext = wcschr(wsText, L'\n');
if (wsNext == nullptr)
{
// No more separator
_selection->Append(ref new String(wsText));
wsText = wsNext;
}
else
{
_selection->Append(ref new String(wsText, wsNext - wsText));
wsText = wsNext + 1;
}
}
e->AcceptedOperation = DataPackageOperation::Copy;
def->Complete();
});
Overview of Changes Made to Correct the Problem
I decided to use coroutines with GetTextAsync()
since I was using the latest build of Visual Studio 2017 Community Edition. To do so required some changes to the method return type from void
to winrt::Windows::Foundation::IAsyncAction
along with a couple of changes to the solution properties and the addition of a couple of include files to allow for the coroutines changes to compile and run properly.
See the answer and notes about several different approaches to concurrency along with the Visual Studio 2017 solution properties changes to use coroutines and the co_await
operator at C++11 threads to update MFC application windows. SendMessage(), PostMessage() required?
At the top of MainPage.cpp I added the following two include directives:
#include <experimental\resumable>
#include <pplawait.h>
I modified the OnListViewDragItemsStarting()
method to look like:
void MainPage::OnListViewDragItemsStarting(Windows::Foundation::IInspectable const & target, Windows::UI::Xaml::Controls::DragItemsStartingEventArgs const & e)
{
// provide the data that we have in the ListView which the user has selected
// to drag to the other ListView. this is the data that will be copied from
// the source ListView to the destination ListView.
auto p = target.try_as<winrt::Windows::UI::Xaml::Controls::ListView>();
unsigned int n = e.Items().Size();
if (p) {
int iIndex = p.SelectedIndex();
e.Data().Properties().Title(hstring (L"my Title"));
e.Data().SetText(DataSource::myDataBase[iIndex].name.c_str());
e.Data().RequestedOperation(winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::Copy);
}
}
Finally I rewrote the method OnListViewDrop()
to use coroutines as follows (also required the return type of the declaration in the class declaration to be changed to agree with the new return type):
winrt::Windows::Foundation::IAsyncAction MainPage::OnListViewDrop(Windows::Foundation::IInspectable const & target, Windows::UI::Xaml::DragEventArgs const & e)
{
// update the destination ListView with the data that was dragged from the
// source ListView. the method GetTextAsync() is an asynch method so
// we are using coroutines to get the result of the operation.
// we need to capture the target ListView before doing the co_await
// in a local variable so that we will know which ListView we are to update.
auto p = target.try_as<winrt::Windows::UI::Xaml::Controls::ListView>();
// do the GetTextAsync() and get the result by using coroutines.
auto ss = co_await e.DataView().GetTextAsync();
// update the ListView control that originally triggered this handler.
p.Items().Append(box_value(ss));
}
User contributions licensed under CC BY-SA 3.0