0
votes

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. screen shot of UWP application with two ListView controls for drag and drop exploration

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.

1

1 Answers

0
votes

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));
}