xaml — обрабатывает событие перетаскивания перетаскивания из ListView в ListView с помощью приложений UWP и C ++ / WinRT

Я работаю над простым приложением UWP, написанным на C ++ / WinRT под Windows 10, которое содержит два ListView управления. Цель этого приложения состоит в том, чтобы узнать, как выбрать элемент из одного ListView управление, перетащите его на другой ListView контролировать и отбросить элемент, чтобы он был скопирован из источника ListView контроль до места назначения ListView контроль.

Все примеры, которые я нашел до сих пор, используют C # с несколькими, использующими C ++ / CX, а не C ++ / WinRT и нативный C ++, однако мне удалось пробиться к точке, где базовая механика выбора элемента из источника ListView работает как перетаскивание на место назначения ListView, Однако при попытке извлечь информацию из события отбрасывания для обновления места назначения 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 что указывает на то, что у меня нет согласия относительно формата данных, которые создает начало перетаскивания и пытается использовать капля перетаскивания.

Обзор исходного кода

Я изменил стандартный шаблон приложения C ++ / WinRT в области MainPage.xml, MainPage.cpp, MainPage.h и pch.h. Я также добавил файлы классов для нового класса 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;
}

Исходный код MainPage.cpp за страницей XAML:

#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 контроль справа.
снимок экрана приложения UWP с двумя элементами управления ListView для исследования методом перетаскивания

Приложение: Ссылки и документация

Документы Microsoft — пространство имен Windows.ApplicationModel.DataTransfer

Документы Microsoft — класс DragItemsStartingEventArgs который содержит ссылку на этот пример проекта, который, похоже, использует C ++ / CX Перетащите образец на GitHub который содержит Окна-универсальный-образцы / Образцы / XamlDragAndDrop / CPP / Scenario1_ListView.xaml.cpp у этого есть полезный пример.

0

Решение

Причиной исключения послужило неправильное использование GetTextAsync() метод, который является асинхронным методом, который требует использования потоков, задач, сопрограмм или некоторых других функций параллелизма.

Я нашел пример исходного кода Окна-универсальный-образцы / Образцы / 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 Community Edition. Для этого необходимо внести некоторые изменения в метод, возвращающий тип из void в winrt::Windows::Foundation::IAsyncAction наряду с парой изменений в свойствах решения и добавлением пары включаемых файлов, позволяющих корректно компилировать и запускать изменения сопрограмм.

См. Ответ и примечания о нескольких различных подходах к параллелизму наряду с изменениями свойств решения Visual Studio 2017 для использования сопрограмм и co_await оператор в Потоки C ++ 11 для обновления окон приложений MFC. SendMessage (), PostMessage () требуется?

Вверху MainPage.cpp я добавил следующие две директивы include:

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

Другие решения

Других решений пока нет …