wpf icon indicating copy to clipboard operation
wpf copied to clipboard

How do you correctly display a WPF derived control in C++/CLI?

Open vsfeedback opened this issue 1 year ago • 1 comments

This issue has been moved from a ticket on Developer Community.


Working C# code is translated to C++/CLI. The two child button controls display correctly but the custom derived control does not display although its outline does appear when tabbing through the three child controls. Are there additional control settings required here which are provided in the background by C#?

using namespace System;
using namespace Windows;
using namespace Controls;
using namespace Primitives;
using namespace Input;
using namespace Windows::Media;
using namespace Shapes;

ref class ColorCell : FrameworkElement
{
public:
    // Dependency properties.
    static initonly DependencyProperty^ IsSelectedProperty;
    static initonly DependencyProperty^ IsHighlightedProperty;

    // Properties.
    void IsSelected(bool value)
    {
        SetValue(IsSelectedProperty, value);
    }

    bool IsSelected()
    {
        return static_cast(GetValue(IsSelectedProperty));
    }

    void IsHighlighted(bool value)
    {
        SetValue(IsHighlightedProperty, value);
    }

    bool IsHighlighted()
    {
        return static_cast(GetValue(IsHighlightedProperty));
    }
            
    Brush^ Brush()
    {
        return brush;
    }
    
    // Constructor requires Color argument.
    ColorCell(Color clr)
    {
        //Visibility = ::Visibility::Visible; // not needed in C#
        //IsEnabled = true; // not needed in C#

        // Create a new DrawingVisual and store as field.
        visColor = gcnew DrawingVisual;
        auto dc = visColor->RenderOpen();

        // Draw a rectangle with the color argument.
        auto rect = Rect(Point(0, 0), sizeCell);
        rect.Inflate(-4, -4);
        auto pen = gcnew Pen(SystemColors::ControlTextBrush, 1);
        brush = gcnew SolidColorBrush(clr);
        dc->DrawRectangle(brush, pen, rect);
        dc->Close();
        
        // AddVisualChild is necessary for event routing!
        AddVisualChild(visColor);
        AddLogicalChild(visColor);
    }

private:
    // Private fields.
    static initonly Size sizeCell = Size(20, 20);
    DrawingVisual^ visColor;
    ::Brush^ brush;

    static ColorCell()
    {
        IsSelectedProperty =
            DependencyProperty::Register("IsSelected", bool::typeid,
                ColorCell::typeid, gcnew FrameworkPropertyMetadata(false,
                    FrameworkPropertyMetadataOptions::AffectsRender));

        IsHighlightedProperty =
            DependencyProperty::Register("IsHighlighted", bool::typeid,
                ColorCell::typeid, gcnew FrameworkPropertyMetadata(false,
                    FrameworkPropertyMetadataOptions::AffectsRender));
    }

protected:
    // Override protected properties and methods for visual child.
    int VisualChildrenCount() // override
    {
        return 1;
    }

    Visual^ GetVisualChild(int index) override
    {
        if (index > 0)
        {
            throw gcnew ArgumentOutOfRangeException("index");
        }

        return visColor;
    }

    // Override protected methods for size and rendering of element.
    Size MeasureOverride(Size sizeAvailable) override
    {
        return sizeCell;
    }

    void OnRender(DrawingContext^ dc) override
    {
        auto rect = Rect(Point(0, 0), RenderSize);
        rect.Inflate(-1, -1);
        auto pen = gcnew Pen(SystemColors::HighlightBrush, 1);

        if (IsHighlighted())
        {
            dc->DrawRectangle(SystemColors::ControlDarkBrush, pen, rect);
        }
        else if (IsSelected())
        {
            dc->DrawRectangle(SystemColors::ControlLightBrush, pen, rect);
        }
        else
        {
            dc->DrawRectangle(Brushes::Transparent, nullptr, rect);
        }
    }
};

ref class ColorGrid : Control
{
public:
    // Public constructor.
    ColorGrid()
    {
        //Visibility = ::Visibility::Visible; // not needed in C#
        //IsEnabled = true; // not needed in C#

        strColors = gcnew array { // multidimensional array 1 constructed ... but persists
            { "Black", "Brown", "DarkGreen", "MidnightBlue",
                "Navy", "DarkBlue", "Indigo", "DimGray" },
            { "DarkRed", "OrangeRed", "Olive", "Green",
                "Teal", "Blue", "SlateGray", "Gray" },
            { "Red", "Orange", "YellowGreen", "SeaGreen",
                "Aqua", "LightBlue", "Violet", "DarkGray" },
            { "Pink", "Gold", "Yellow", "Lime",
                "Turquoise", "SkyBlue", "Plum", "LightGray" },
            { "LightPink", "Tan", "LightYellow", "LightGreen",
                "LightCyan", "LightSkyBlue", "Lavender", "White" }
        };

        cells = gcnew array(yNum, xNum); // multidimensional array 2 constructed ... but persists

        // Create a Border for the control.
        bord = gcnew Border;
        bord->BorderBrush = SystemColors::ControlDarkDarkBrush;
        bord->BorderThickness = Thickness(1);
        AddVisualChild(bord);           // necessary for event routing.
        AddLogicalChild(bord);

        // Create a UniformGrid as a child of the Border.
        unigrid = gcnew UniformGrid;
        unigrid->Background = SystemColors::WindowBrush;
        unigrid->Columns = xNum;
        bord->Child = unigrid;

        // Fill up the UniformGrid with ColorCell objects.
        for (int y = 0; y < yNum; y++)
            for (int x = 0; x < xNum; x++)
            {
                auto clr = static_cast(Colors::typeid->GetProperty(strColors[y, x])->GetValue(nullptr, nullptr));

                cells[y, x] = gcnew ColorCell(clr);
                unigrid->Children->Add(cells[y, x]);

                if (clr == SelectedColor())
                {
                    cellSelected = cells[y, x];
                    cells[y, x]->IsSelected(true);
                }

                auto tip = gcnew ::ToolTip;
                tip->Content = strColors[y, x];
                cells[y, x]->ToolTip = tip;
            }
    }

    // Public get-only SelectedColor property.
    Color SelectedColor()
    {
        return clrSelected;
    }

    // Public "Changed" event.
    event EventHandler^ SelectedColorChanged;


private:
    // Number of rows and columns.
    const int yNum = 5;
    const int xNum = 8;

    // The colors to be displayed.
    array^ strColors; // multidimensional array 1 declared

    // The ColorCell objects to be created.
    array^ cells;  // multidimensional array 2 declared
    ColorCell^ cellSelected;
    ColorCell^ cellHighlighted;

    // Elements that comprise this control.
    Border^ bord;
    UniformGrid^ unigrid;

    // Currently selected color.
    Color clrSelected = Colors::Black;

protected:
    // Override of VisualChildrenCount.
    int VisualChildrenCount() // override
    {
        return 1;
    }

    Visual^ GetVisualChild(int index) override
    {
        if (index > 0)
        {
            throw gcnew ArgumentOutOfRangeException("index");
        }

        return bord;
    }

    // Override of MeasureOverride.
    Size MeasureOverride(Size sizeAvailable) override
    {
        bord->Measure(sizeAvailable);
        return bord->DesiredSize;
    }

    // Override of ArrangeOverride.
    Size ArrangeOverride(Size sizeFinal) override
    {
        bord->Arrange(Rect(Point(0, 0), sizeFinal));
        return sizeFinal;
    }

    // Mouse event handling.
    void OnMouseEnter(MouseEventArgs^ args) override
    {
        Control::OnMouseEnter(args);

        if (cellHighlighted != nullptr)
        {
            cellHighlighted->IsHighlighted(false);
            cellHighlighted = nullptr;
        }
    }
    
    void OnMouseMove(MouseEventArgs^ args) override
    {
        Control::OnMouseMove(args);

        auto cell = static_cast(args->Source);

        if (cell != nullptr)
        {
            if (cellHighlighted != nullptr)
            {
                cellHighlighted->IsHighlighted(false);
            }

            cellHighlighted = cell;
            cellHighlighted->IsHighlighted(true);
        }
    }
    
    void OnMouseLeave(MouseEventArgs^ args) override
    {
        Control::OnMouseLeave(args);

        if (cellHighlighted != nullptr)
        {
            cellHighlighted->IsHighlighted(false);
            cellHighlighted = nullptr;
        }
    }

    void OnMouseDown(MouseButtonEventArgs^ args) override
    {
        Control::OnMouseDown(args);

        auto cell = static_cast(args->Source);

        if (cell != nullptr)
        {
            if (cellHighlighted != nullptr)
            {
                cellHighlighted->IsSelected(false);
            }

            cellHighlighted = cell;
            cellHighlighted->IsSelected(true);
        }
        Focus();
    }

    void OnMouseUp(MouseButtonEventArgs^ args) override
    {
        Control::OnMouseUp(args);

        auto cell = static_cast(args->Source);

        if (cell != nullptr)
        {
            if (cellSelected != nullptr)
            {
                cellSelected->IsSelected(false);
            }

            cellSelected = cell;
            cellSelected->IsSelected(true);

            clrSelected = static_cast(cellSelected->Brush())->Color;
            OnSelectedColorChanged(EventArgs::Empty);
        }
    }
    
    // Keyboard event handling.
    void OnGotKeyboardFocus(
        KeyboardFocusChangedEventArgs^ args) override
    {
        Control::OnGotKeyboardFocus(args);

        if (cellHighlighted == nullptr)
        {
            if (cellSelected != nullptr)
            {
                cellHighlighted = cellSelected;
            }
            else
            {
                cellHighlighted = cells[0, 0];
            }

            cellHighlighted->IsHighlighted(true);
        }
    }

    void OnLostKeyboardFocus(
        KeyboardFocusChangedEventArgs^ args) override
    {
        Control::OnGotKeyboardFocus(args);

        if (cellHighlighted != nullptr)
        {
            cellHighlighted->IsHighlighted(false);
            cellHighlighted = nullptr;
        }
    }

    void OnKeyDown(KeyEventArgs^ args) override
    {
        Control::OnKeyDown(args);

        int index = unigrid->Children->IndexOf(cellHighlighted);
        int y = index / xNum;
        int x = index % xNum;

        switch (args->Key)
        {
        case Key::Home:
            y = 0;
            x = 0;
            break;

        case Key::End:
            y = yNum - 1;
            x = xNum + 1;
            break;

        case Key::Down:
            if ((y = (y + 1) % yNum) == 0)
            {
                x++;
            }
            break;

        case Key::Up:
            if ((y = (y + yNum - 1) % yNum) == yNum - 1)
            {
                x--;
            }
            break;

        case Key::Right:
            if ((x = (x + 1) % xNum) == 0)
            {
                y++;
            }
            break;

        case Key::Left:
            if ((x = (x + xNum - 1) % xNum) == xNum - 1)
            {
                y--;
            }
            break;

        case Key::Enter:
        case Key::Space:
            if (cellSelected != nullptr)
            {
                cellSelected->IsSelected(false);
            }

            cellSelected = cellHighlighted;
            cellSelected->IsSelected(true);
            clrSelected = static_cast(cellSelected->Brush())->Color;
            OnSelectedColorChanged(EventArgs::Empty);
            break;

        default:
            return;
        }
        if (x >= xNum || y >= yNum)
        {
            MoveFocus(gcnew TraversalRequest(
                FocusNavigationDirection::Next));
        }

        else if (x < 0 || y < 0)
        {
            MoveFocus(gcnew TraversalRequest(
                FocusNavigationDirection::Previous));
        }
        else
        {
            cellHighlighted->IsHighlighted(false);
            cellHighlighted = cells[y, x];
            cellHighlighted->IsHighlighted(true);
        }
        args->Handled = true;
    }
    
    // Protected method to fire SelectedColorChanged event.
    virtual void OnSelectedColorChanged(EventArgs^ args)
    {
        //if (SelectedColorChanged != nullptr) // C++/CLI helper function emitted automatically checks
        {
            SelectedColorChanged(this, args);
        }
    }
};

ref class SelectColor : Window
{
public:
    SelectColor()
    {
        Title = "Select Color";
        SizeToContent = ::SizeToContent::WidthAndHeight;

        // Create StackPanel as content of window.
        auto stack = gcnew StackPanel;
        stack->Orientation = Orientation::Horizontal;
        Content = stack;
        //stack->Visibility = ::Visibility::Visible; // ???

        // Create do-nothing button to test tabbing.
        auto btn = gcnew Button;
        btn->Content = "Do-nothing button\nto test tabbing";
        btn->Margin = Thickness(24);
        btn->HorizontalAlignment = ::HorizontalAlignment::Center;
        btn->VerticalAlignment = ::VerticalAlignment::Center;
        stack->Children->Add(btn);

        // Create ColorGrid control.
        auto clrgrid = gcnew ColorGrid;
        clrgrid->Margin = Thickness(24);
        clrgrid->HorizontalAlignment = ::HorizontalAlignment::Center;
        clrgrid->VerticalAlignment = ::VerticalAlignment::Center;
        clrgrid->SelectedColorChanged += gcnew EventHandler(this, &SelectColor::ColorGridOnSelectedColorChanged);
        stack->Children->Add(clrgrid);
        //clrgrid->Visibility = ::Visibility::Visible; // ???

        // Create another do-nothing button.
        btn = gcnew Button;
        btn->Content = "Do-nothing button\nto test tabbing";
        btn->Margin = Thickness(24);
        btn->HorizontalAlignment = ::HorizontalAlignment::Center;
        btn->VerticalAlignment = ::VerticalAlignment::Center;
        stack->Children->Add(btn);
    }

private:
    void ColorGridOnSelectedColorChanged(Object^ sender, EventArgs^ args)
    {
        auto clrgrid = static_cast(sender);
        Background = gcnew SolidColorBrush(clrgrid->SelectedColor());
    }
};

int main(array^ args)
{
    auto app = gcnew Application;
    app->Run(gcnew SelectColor);

    return 0;
}

Original Comments

Feedback Bot on 8/1/2022, 08:07 AM:

(private comment, text removed)

Feedback Bot on 11/3/2022, 11:25 AM:

(private comment, text removed)


Original Solutions

(no solutions)

vsfeedback avatar Jul 12 '24 09:07 vsfeedback

Could we have a repro project?

miloush avatar Jul 12 '24 10:07 miloush