【问题标题】:handle drop event of drag and drop from ListView to ListView with UWP and C++/WinRT app使用 UWP 和 C++/WinRT 应用处理从 ListView 拖放到 ListView 的拖放事件
【发布时间】:2018-12-23 06:32:31
【问题描述】:

我正在开发一个在 Windows 10 下用 C++/WinRT 编写的简单 UWP 应用程序,其中包含两个 ListView 控件。此应用程序的目标是学习如何从一个ListView 控件中选择一个项目,将其拖动到另一个ListView 控件,然后放下该项目,以便将其从源ListView 控件复制到目标@ 987654331@控制。

到目前为止,我发现的所有示例都使用 C#,其中一些使用 C++/CX,而不是 C++/WinRT 和本机 C++,但是我已经设法了解了从源中选择项目的基本机制ListView 的工作原理与拖放到目标 ListView 一样。但是,当尝试从 drop 事件中获取信息以更新目标 ListView 时,我遇到了异常。

问题:我需要进行哪些更改才能将源ListView 控件中的选定文本拖放到目标ListView 控件上,然后将文本添加到目的地ListView控制?

Visual Studio 2017 的输出窗口显示以下文本,我将其解释为错误地址异常:

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.

执行函数void MainPage::OnListViewDrop()(MainPage.cpp 源文件中的最后一个函数)中的以下源代码行时引发异常:

auto x = e.DataView().GetTextAsync();

附加信息 A: 使用调试器时,我发现与异常相关的错误消息意味着方法 OnListViewDragItemsStarting() 提供的数据中存在错误。异常错误信息的文本是:

{m_handle={m_value=0x05550330 L"DataPackage does not contain the specified format. Verify its presence using DataPackageView.Contains or DataPackageView.AvailableFormats." } }

我还在Visual Studio第一次抛出并捕获异常的站点上发现,在base.h停止应用程序(来自C++/WinRT模板),错误文本0x8004006a : Invalid clipboard format表明我缺少就拖动开始创建的数据格式和拖动尝试消耗的数据格式达成一致。

源代码概览

我在 MainPage.xml、MainPage.cpp、MainPage.h 和 pch.h 区域修改了标准 C++/WinRT 应用程序模板。我还为一个新类 DataSource 添加了类文件,它使用std::vector<> 来包含一些测试数据。此内存驻留数据在 App 构造函数中使用一些虚拟数据进行初始化:

App::App()
{
    InitializeComponent();
    DataSource::InitializeDataBase();
    Suspending({ this, &App::OnSuspending });
    //  … other code

首先我必须在 pch.h 文件中添加一行来提供拖放模板:

#include "winrt/Windows.ApplicationModel.DataTransfer.h"    // ADD_TO:  need to add to allow use of drag and drop in MainPage.cpp

XAML 源文件包含两个ListView 控件的源以及一个TextBlock 控件,该控件显示源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>

DataSource 的类声明很简单。类定义如下:

#pragma once
class DataSource
{
public:
    DataSource();
    ~DataSource();

    static int InitializeDataBase();

    struct DataSourceType
    {
        std::wstring  name;
        std::wstring  description;
    };

    static std::vector<DataSourceType> myDataBase;

}

;

以及vector的初始化,在应用启动时App构造时完成的是:

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

XAML 页面背后的 MainPage.cpp 源代码是:

#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.
    }

}

在开始拖动之前,在源ListView 中选择了一个项目的应用程序的屏幕截图如下所示。源ListView 控件位于左侧,目标ListView 控件位于右侧。

附录:参考资料和文档

Microsoft Docs - Windows.ApplicationModel.DataTransfer Namespace

Microsoft Docs - DragItemsStartingEventArgs Class 包含指向此示例项目的链接,该示例项目看起来正在使用 C++/CX Drag and drop sample on GitHub 其中包含有一个有用示例的Windows-universal-samples/Samples/XamlDragAndDrop/cpp/Scenario1_ListView.xaml.cpp

【问题讨论】:

    标签: c++ xaml uwp drag-and-drop c++-winrt


    【解决方案1】:

    异常的原因是滥用 GetTextAsync() 方法,这是一种异步方法,需要使用线程、任务、协程或其他一些并发功能。

    我找到了示例源代码Windows-universal-samples/Samples/XamlDragAndDrop/cpp/Scenario1_ListView.xaml.cpp,它提供了关于我做错了什么的提示。另见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();
        });
    

    为纠正问题所做的更改概述

    我决定将协程与 GetTextAsync() 一起使用,因为我使用的是 Visual Studio 2017 社区版的最新版本。为此,需要将方法返回类型从void 更改为winrt::Windows::Foundation::IAsyncAction,同时对解决方案属性进行一些更改,并添加一些包含文件以使协程更改能够正确编译和运行。

    C++11 threads to update MFC application windows. SendMessage(), PostMessage() required?C++11 threads to update MFC application windows. SendMessage(), PostMessage() required? 上查看有关几种不同并发方法以及 Visual Studio 2017 解决方案属性更改以使用协程和 co_await 运算符的答案和注释

    在 MainPage.cpp 的顶部我添加了以下两个包含指令:

    #include <experimental\resumable>
    #include <pplawait.h>
    

    我将OnListViewDragItemsStarting() 方法修改为如下所示:

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

    最后我重写了OnListViewDrop()方法使用协程如下(还要求类声明中声明的返回类型要改成与新的返回类型一致):

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

    【讨论】:

      猜你喜欢
      • 2017-05-14
      • 1970-01-01
      • 1970-01-01
      • 2011-06-16
      • 2019-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-26
      相关资源
      最近更新 更多