ステップ2 - ドラッグアンドドロップ機能を実装する

このステップでは、ドラッグアンドドロップ機能を実装します。

ドラッグ機能を作成する

このセクションでは、まず Kanzi Engine APIを使用して、チュートリアルの前のステップで作成した Drag Item プレハブテンプレートをインスタンス化します。次に、ユーザーがドラッグアンドドロップジェスチャーを開始し、ボタンをドラッグしたときの動作を定義します。最後に、ナビゲーションバー内の各ボタンに対してドラッグアンドドロップマニピュレータを作成し、構成します。

ドラッグ機能を作成するには、以下のようにします。

  1. Visual Studioで、drag_and_drop.cpp ファイルの Application::onProjectLoaded 関数内で、ユーザーがドラッグしているボタンを視覚化するために使用する Drag Item プレハブをインスタンス化します。

        void onProjectLoaded() override
        {
            ...
    
            // Get the reference to the Drag Item prefab.
            ResourceManager* resourceManager = getDomain()->getResourceManager();
            PrefabTemplateSharedPtr dragItemPrefab = resourceManager->acquireResource<PrefabTemplate>("kzb://drag_and_drop/Prefabs/Drag Item");
    
            // Instantiate the Drag Item prefab.
            m_dragItem = dragItemPrefab->instantiate<Node2D>("Drag Item");
    
            // Get the RootPage node using its alias.
            Node2DSharedPtr rootPage = screen->lookupNode<Node2D>("#RootPage");
    
            // Add to the RootPage node the instance of the Drag Item prefab that you created.
            rootPage->addChild(m_dragItem);
    
            // Disable the Visible property of the Drag Item prefab instance.
            // You hide the Drag Item when the user is not dragging it.
            m_dragItem->setVisible(false);
        }
    
    private:
        ...
    
        // Define a member variable for the instantiated Drag Item.
        Node2DSharedPtr m_dragItem;
    
  2. DragAndDrop クラスのプライベートセクションに、Drag Item プレハブインスタンスを移動する関数を作成します。

    private:
    
        ...
    
        // Update the position of the Drag Item.
        void updateDragAndDrop(Vector2 dragPosition, Matrix3x3 dragWorldTransform)
        {
            // Calculate the local drag anchor that is the top-left corner of the button that the user is dragging.
            Vector2 localDragAnchor = dragPosition - m_dragGrabOffset;
    
            // Restrict the movement of the Drag Item to the x axis.
            localDragAnchor.setY(0.0f);
    
            // Calculate the global drag anchor.
            Vector2 globalDragAnchor = dragWorldTransform * localDragAnchor;
    
            // The structure that you use to describe the Render Transformation property of the Drag Item.
            SRTValue2D transform;
            // Set the Render Transformation property Translation property field to the global drag anchor.
            transform.setTranslation(globalDragAnchor);
    
            // Move the Drag Item for the amount of the dragged distance.
            m_dragItem->setRenderTransformation(transform);
    
            // Set the icons of the buttons.
            updateItems();
        }
    
        ...
    
        // Define a member variable for the offset from the top-left corner of the button where the user pressed down or clicked it.
        Vector2 m_dragGrabOffset;
    };
    
  3. ドラッグメッセージのハンドラーを追加します。

    1. ユーザーがボタンをドラッグし始めたときの動作を定義するには、DragAndDrop クラスのプライベートセクションで DragAndDropManipulator::StartedMessage メッセージのハンドラーを定義します。

      private:
      
          ...
      
          // Define the handler for the DragAndDropManipulator::StartedMessage message from 2D nodes
          // that have an input manipulator which generates drag-and-drop messages.
          // This prepares the 2D node for dragging.
          void onDragStarted(DragAndDropManipulator::StartedMessageArguments& messageArguments)
          {
              // Get from the message arguments the button that the user starts to drag.
              Node2DSharedPtr dragSourceItem = dynamic_pointer_cast<Node2D>(messageArguments.getSource());
      
              // Get the size of the button that the user starts to drag.
              Vector2 dragSourceItemSize = dragSourceItem->getActualSize();
      
              // Set the size of the Drag Item to be the same as the size of that button.
              m_dragItem->setSize(dragSourceItemSize.getX(), dragSourceItemSize.getY());
      
              // Move the Drag Item to the correct position.
              updateDragAndDrop(messageArguments.getPoint(), dragSourceItem->getWorldTransform());
      
              // Make the Drag Item visible.
              m_dragItem->setVisible(true);
          }
      
          ...
      
    2. ユーザーがボタンをドラッグしているときの動作を定義するには、onDragStarted 関数の後に DragAndDropManipulator::MovedMessage メッセージのハンドラーを定義します。

      // Define the handler for the DragAndDropManipulator::MovedMessage message from 2D nodes
      // that have an input manipulator which generates drag-and-drop messages.
      void onDragMoved(DragAndDropManipulator::MovedMessageArguments& messageArguments)
      {
          // Get from the message arguments the button that the user is dragging.
          Node2DSharedPtr dragSourceItem = dynamic_pointer_cast<Node2D>(messageArguments.getSource());
      
          // Move the Drag Item and update the icons of the buttons.
          updateDragAndDrop(messageArguments.getPoint(), dragSourceItem->getWorldTransform());
      }
      
  4. 各ボタンに対してドラッグアンドドロップマニピュレータを作成して設定します。

    1. DragAndDrop クラスのプライベートセクションに、ノード用のドラッグアンドドロップマニピュレータを作成および設定する関数を追加します。

      // Create and configure a drag-and-drop manipulator for a node.
      void createDragAndDropManipulator(NodeSharedPtr dragSourceItem)
      {
          Domain* domain = getDomain();
      
          // Create an input manipulator that generates drag-and-drop messages.
          DragAndDropManipulatorSharedPtr dragAndDropManipulator = DragAndDropManipulator::create(domain);
      
          // Add the input manipulator to the node.
          dragSourceItem->addInputManipulator(dragAndDropManipulator);
      
          // Set the duration of the long press before the drag-and-drop starts to 200 ms. The default is 500 ms.
          // This is the amount of time the user must press the node before they can start dragging it.
          dragAndDropManipulator->setPressDuration(chrono::milliseconds(200));
      
          // Subscribe to the DragAndDropManipulator::StartedMessage message at the node.
          // The DragAndDropManipulator generates this message when the user presses the node
          // for the duration set by DragAndDropManipulator::setPressDuration.
          dragSourceItem->addMessageHandler(DragAndDropManipulator::StartedMessage, bind(&DragAndDrop::onDragStarted, this, placeholders::_1));
      
          // Subscribe to the DragAndDropManipulator::MovedMessage message at the node.
          // The DragAndDropManipulator generates this message when the pointer moves.
          dragSourceItem->addMessageHandler(DragAndDropManipulator::MovedMessage, bind(&DragAndDrop::onDragMoved, this, placeholders::_1));
      }
      
    2. Application::onProjectLoaded 関数の最後で、各ボタンに対して createDragAndDropManipulator 関数を呼び出します。

      void onProjectLoaded() override
      {
          ...
      
          // Create a drag-and-drop manipulator for each button.
          // You get the button nodes using their aliases.
          createDragAndDropManipulator(screen->lookupNode<Node>("#Navigation"));
          createDragAndDropManipulator(screen->lookupNode<Node>("#Phone"));
          createDragAndDropManipulator(screen->lookupNode<Node>("#Applications"));
          createDragAndDropManipulator(screen->lookupNode<Node>("#Music"));
          createDragAndDropManipulator(screen->lookupNode<Node>("#Car"));
      }
      
  5. アプリケーションをビルドして実行します。

    アプリケーション内でボタンを長押しし、水平方向にドラッグします。

    Drag Item プレハブのインスタンスをドラッグします。Drag Item の左側が入力ポインタに位置し、Drag Item はまだアイコンを表示していません。次のセクションでドラッグ機能を完成させます。

ドラッグ機能を完了する

このセクションでは、まずユーザーがドラッグするボタンのアイコンと位置を設定します。その後、ユーザーがボタンの1つをドラッグすると、ボタンのアイコンを再配置します。

ドラッグ機能を完了するには、

  1. onDragStarted 関数内で updateDragAndDrop 関数を呼び出す前に、Drag Item のアイコンを設定し、ノードを正しく配置します。

    void onDragStarted(DragAndDropManipulator::StartedMessageArguments& messageArguments)
        {
            ...
    
            // Get the data context object of the button that the user starts to drag.
            m_draggedDataContext = dynamic_pointer_cast<DataObject>(dragSourceItem->getProperty(DataContext::DataContextProperty));
    
            // Set the Data Context property of the Drag Item to the data context of the button.
            // This way you set the Drag Item to have the same icon as the button that the user starts to drag.
            m_dragItem->setProperty(DataContext::DataContextProperty, m_draggedDataContext);
    
            // Save the point from which the user started dragging the node,
            // relative to the origin (by default the top-left corner) of the node.
            m_dragGrabOffset = messageArguments.getPoint();
    
            ...
        }
    
        ...
    
        // Define a member variable for the data context object of the button that the user drags.
        DataObjectSharedPtr m_draggedDataContext;
    };
    
  2. updateDragAndDrop 関数内で、updateItems() 関数を呼び出す前に、ユーザーがボタンの1つをドラッグしたときにすべてのボタンのアイコンを再配置するコードを追加してください。

    void updateDragAndDrop(Vector2 dragPosition, Matrix3x3 dragWorldTransform)
    {
        ...
    
        // Get the global pointer position.
        Vector2 globalPointerPosition = dragWorldTransform * dragPosition;
    
        // Convert global coordinates to local coordinates of the Grid Layout 2D node.
        Vector2 hitTestPoint = *m_grid->globalToLocal(globalPointerPosition);
    
        // Get the width of the button.
        float cellWidth = m_grid->getActualColumnSize(0);
    
        // Calculate the index of the button in the Grid Layout 2D node.
        unsigned int cellIndex = 0;
    
        if (hitTestPoint.getX() > 0.0f)
        {
            cellIndex = floatToUint(hitTestPoint.getX() / cellWidth);
            cellIndex = min(cellIndex, static_cast<unsigned int>(m_grid->getChildCount() - 1u));
        }
    
        // Remove the data object from the old position.
        m_rootData->removeChild(*m_draggedDataContext);
    
        // Insert the data object to the new position.
        m_rootData->insertChild(cellIndex, m_draggedDataContext);
    
        ...
    }
    
  3. for ループ内の updateItems() 関数で、ユーザーがボタンをドラッグしている間にアイコンを非表示にする if-else 節を追加します。

    void updateItems()
    {
        ...
    
        for (; dataIt != endDataIt; dataIt++, nodeIt++)
        {
            ...
    
            // If the button node is the one that the user is dragging, hide it.
            // You hide the node because you use the Drag Item to visualize the dragging of the node.
            if (m_draggedDataContext && itemData == m_draggedDataContext)
            {
                itemNode->setVisible(false);
            }
            else
            {
                itemNode->setVisible(true);
            }
        }
    }
    

ドロップ機能を作成する

前のセクションでは、ボタンのドラッグを実装しました。ユーザーがドラッグアンドドロップのジェスチャーを終了すると、Drag Item はユーザーがポインタを離した正確な位置に表示されたままになります。このセクションでは、ユーザーがボタンをドロップしたときに、ボタンがその位置に落ちるように見せるためのコードを追加します。

ドロップ機能を作成するには、以下のようにします。

  1. ユーザーがノードのドラッグを停止し、ポインターを離したときの動作を定義するには、onDragMoved 関数の後に DragAndDropManipulator::FinishedMessage メッセージのハンドラーを定義します。

    // Define the handler for the DragAndDropManipulator::FinishedMessage message from 2D nodes
    // that have an input manipulator which generates drag-and-drop messages.
    void onDragFinished(DragAndDropManipulator::FinishedMessageArguments&)
    {
        // Hide the Drag Item prefab instance.
        m_dragItem->setVisible(false);
    
        // Clear the pointer to the data context object.
        m_draggedDataContext.reset();
    
        // Assign the correct icons to the buttons.
        updateItems();
    }
    
  2. createDragAndDropManipulator 関数の最後で、ノードの DragAndDropManipulator::FinishedMessage メッセージを購読します。

    // Create and configure a drag-and-drop manipulator for a node.
    void createDragAndDropManipulator(NodeSharedPtr dragAndDropNode)
    {
        ...
    
        // Subscribe to the DragAndDropManipulator::FinishedMessage message at the node.
        // The DragAndDropManipulator generates this message when the user ends the drag-and-drop gesture
        // by releasing the pointer.
        dragSourceItem->addMessageHandler(DragAndDropManipulator::FinishedMessage, bind(&DragAndDrop::onDragFinished, this, placeholders::_1));
    }
    
  3. アプリケーションをビルドして実行します。

    ドラッグアンドドロップジェスチャーを終了すると、Drag Item は見えなくなります。

関連項目

ドラッグアンドドロップ入力マニピュレータについて詳しく知るには、Drag-And-Drop Manipulator を使用する を参照してください。

Kanzi でのユーザー入力の処理について詳しく学ぶには、ユーザー入力の処理 を参照してください。