欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

WPF自定義Panel的示例詳解

 更新時間:2024年03月27日 11:21:14   作者:趨時軟件  
WPF中默認(rèn)的拖放操作可能并不是那么好用,為了解決這個問題,我們可以自定義一個?Panel?來實現(xiàn)更簡單的拖拽操作,下面我們就來看看具體實現(xiàn)方法吧

在 WPF 應(yīng)用程序中,拖放操作是實現(xiàn)用戶交互的重要組成部分。通過拖放操作,用戶可以輕松地將數(shù)據(jù)從一個位置移動到另一個位置,或者將控件從一個容器移動到另一個容器。然而,WPF 中默認(rèn)的拖放操作可能并不是那么好用。為了解決這個問題,我們可以自定義一個 Panel 來實現(xiàn)更簡單的拖拽操作。

自定義 Panel 的優(yōu)點有很多。首先,我們可以根據(jù)自己的需求來設(shè)計 Panel 的外觀和行為。其次,我們可以使用代碼來控制拖放操作的細(xì)節(jié),比如拖放的開始和結(jié)束位置、拖放過程中控件的顯示方式等等。最后,我們可以將自定義 Panel 作為一個控件,方便地應(yīng)用到不同的應(yīng)用程序中。

在本教程中,我們將一步一步地創(chuàng)建一個自定義 Panel 來實現(xiàn)更簡單的拖拽操作。我們將學(xué)習(xí)如何定義 Panel 的布局、如何處理拖放事件,以及如何將自定義 Panel 應(yīng)用到不同的應(yīng)用程序中。按照本教程的步驟操作,您將能夠創(chuàng)建一個功能強大且易于使用的自定義 Panel,從而使您的 WPF 應(yīng)用程序更加友好和易用。

1.定義一個繼承自Panel的類

public class DragStackPanel : Panel
{
    /// <summary>
    /// 獲取或設(shè)置方向
    /// </summary>
    public Orientation Orientation
    {
        get { return (Orientation)GetValue(OrientationProperty); }
        set { SetValue(OrientationProperty, value); }
    }

    public static readonly DependencyProperty OrientationProperty =
        DependencyProperty.Register("Orientation", typeof(Orientation), typeof(DragStackPanel), new PropertyMetadata(Orientation.Vertical));
}

2.重寫Panel類的MeasureOverride方法測量控件Size

public class DragStackPanel : Panel
{
    protected override Size MeasureOverride(Size availableSize)
    {
        var panelDesiredSize = new Size();
        foreach (UIElement child in InternalChildren)
        {
            child.Measure(availableSize);
            if (this.Orientation == Orientation.Horizontal)
            {
                panelDesiredSize.Width += child.DesiredSize.Width;
                panelDesiredSize.Height = double.IsInfinity(availableSize.Height) ? child.DesiredSize.Height : availableSize.Height;
            }
            else
            {
                panelDesiredSize.Width = double.IsInfinity(availableSize.Width) ? child.DesiredSize.Width : availableSize.Width;
                panelDesiredSize.Height += child.DesiredSize.Height;
            }
        }
        return panelDesiredSize;
    }
}

3.重寫Panel類的ArrangeOverride方法排列控件位置

public class DragStackPanel : Panel
{
    protected override Size ArrangeOverride(Size finalSize)
    {
        double x = 0, y = 0;
        foreach (FrameworkElement child in InternalChildren)
        {
            // 坐標(biāo)
            var position = new Point(x, y);
            // 寬度
            var width = child.DesiredSize.Width;
            // 高度
            var height = child.DesiredSize.Height;
            // 通過排列方向計算寬度和高度
            if (this.Orientation == Orientation.Vertical)
            {
                width = finalSize.Width;
            }
            else
            {
                height = finalSize.Height;
            }

            // 尺寸
            var size = new Size(width, height);
            // 排列位置及尺寸
            child.Arrange(new Rect(position, size));

            // 計算位置
            if (this.Orientation == Orientation.Horizontal)
            {
                x += child.DesiredSize.Width;
            }
            else
            {
                y += child.DesiredSize.Height;
            }
        }

        return finalSize;
    }
}

查看運行效果

<UniformGrid Rows="2">
    <local:DragStackPanel Orientation="Horizontal">
        <Button>test1</Button>
        <Button>test2</Button>
    </local:DragStackPanel>
    <local:DragStackPanel Orientation="Vertical">
        <Button>test3</Button>
        <Button>test4</Button>
    </local:DragStackPanel>
</UniformGrid>

4.重寫PreviewMouseLeftButtonDown方法

該方法在按下鼠標(biāo)左鍵時觸發(fā),我們需要在該方法中獲取第一次按下鼠標(biāo)的坐標(biāo),并且通過命中測試找到我們要拖拽的控件,最后還要在裝飾層中添加一個元素,該元素的背景用原控件的外觀來填充(VisualBrush),這樣就可以覆蓋原來的控件,以便在拖拽控件時能跨越控件的邊界。以下為參考代碼:

public class DragStackPanel : Panel
{
    private FrameworkElement draggingElement;
    private Point mouseRelativePosition;
    private int draggingElementzIndex;
    protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
    {
        // 獲取鼠標(biāo)相對于Panel的坐標(biāo)
        var mousePosition = e.GetPosition(this);
        // 通過命中測試獲取當(dāng)前鼠標(biāo)位置下的元素
        var hitTestResult = this.InputHitTest(mousePosition) as FrameworkElement;
        // 通過命中測試結(jié)果找到當(dāng)前拖拽的控件子項
        draggingElement = FindChild(hitTestResult);
        if (draggingElement != null && this.InternalChildren.Contains(draggingElement))
        {
            // 記錄鼠標(biāo)相對位置,以供后續(xù)使用
            mouseRelativePosition = e.GetPosition(draggingElement);

            // 暫存ZIndex
            draggingElementzIndex = Panel.GetZIndex(draggingElement);
            // 將ZIndex置頂
            Panel.SetZIndex(draggingElement, this.InternalChildren.Count);
            // 添加遮罩,防止拖拽時覆蓋
            AddOverlay(draggingElement);

            e.Handled = true;
        }

        base.OnPreviewMouseLeftButtonDown(e);
    }
}

5.重寫PreviewMouseMove方法

該方法在鼠標(biāo)移動時觸發(fā),我們需要在鼠標(biāo)被按下移動時,根據(jù)當(dāng)前的坐標(biāo)與第一次按下的坐標(biāo)實時計算出被拖拽元素的偏移量,這樣該元素就能跟隨鼠標(biāo)移動,實現(xiàn)拖拽效果。以下為參考代碼:

public class DragStackPanel : Panel
{
    private FrameworkElement draggingElement;
    private Point mouseRelativePosition;
    private int draggingElementzIndex;
    protected override void OnPreviewMouseMove(MouseEventArgs e)
    {
        var mousePosition = e.GetPosition(this);
        if (e.LeftButton == MouseButtonState.Pressed && draggingElement != null)
        {
            // 當(dāng)前拖拽控件置為不可鼠標(biāo)命中,以供命中下一層的換位控件
            draggingElement.IsHitTestVisible = false;
            // 判斷當(dāng)前拖拽的控件是否為頂層控件
            if (Panel.GetZIndex(draggingElement) == this.InternalChildren.Count)
            {
                // 計算出當(dāng)前拖拽控件相對于this的位置(控件左上角)
                var targetPosition = new Point(mousePosition.X - mouseRelativePosition.X - draggingElement.Margin.Left, mousePosition.Y - mouseRelativePosition.Y - draggingElement.Margin.Top);
                // 獲取當(dāng)前拖拽控件在this中的原始位置
                var draggingElementOriginalPosition = GetDraggingElementOriginalPosition(draggingElement);
                // 計算拖拽控件移動時的偏移量
                var offset = new Point(targetPosition.X - draggingElementOriginalPosition.X, targetPosition.Y - draggingElementOriginalPosition.Y);
                // 應(yīng)用位移
                draggingElement.RenderTransform = new TranslateTransform(offset.X, offset.Y);
            }
            
             e.Handled = true;
        }
        base.OnPreviewMouseMove(e);
    }
}

6.重寫PreviewMouseLeftButtonUp方法。

該方法在鼠標(biāo)左健抬起時觸發(fā),我們需要在該方法中將一些參數(shù)重置。

public class DragStackPanel : Panel
{
    private FrameworkElement draggingElement;
    private Point mouseRelativePosition;
    private int draggingElementzIndex;
    protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
    {
        mouseRelativePosition = default;
        RemoveOverlay(draggingElement);
        Panel.SetZIndex(draggingElement, draggingElementzIndex);
        draggingElement.IsHitTestVisible = true;
        draggingElement.RenderTransform = null;
        draggingElement = null;
        e.Handled = true;
        base.OnPreviewMouseLeftButtonUp(e);
    }
}

以下為運行效果:

7.處理控件的拖拽換位

拖拽換位的思路就是將當(dāng)前正在拖拽的元素放置到新的Index中,并把該Index后面的所有元素整體后移一位。該功能在PreviewMouseMove方法中實現(xiàn)。

public class DragStackPanel : Panel
{
    private FrameworkElement draggingElement;
    private FrameworkElement hitElement;
    private Point mouseRelativePosition;
    private int draggingElementzIndex;
    protected override void OnPreviewMouseMove(MouseButtonEventArgs e)
    {
        ...
        // 命中當(dāng)前拖拽控件的下一層控件
        var hitTestResult = this.InputHitTest(mousePosition) as FrameworkElement;
        // 查找被命中的下一層換位控件
        hitElement = FindChild(hitTestResult);

        // 判斷是否有效
        if (hitElement != null && this.InternalChildren.Contains(hitElement))
        {
            // 應(yīng)用換位
            MoveChild(draggingElement, hitElement);
        }
    }

    private void MoveChild(FrameworkElement element1, FrameworkElement element2)
    {
        var index1 = this.InternalChildren.IndexOf(element1);
        var index2 = this.InternalChildren.IndexOf(element2);
        if (index1 >= 0 && index2 >= 0)
        {
            this.InternalChildren.RemoveAt(index1);
            this.InternalChildren.Insert(index2, element1);
        }
    }
}

在ArrangeOverride方法中處理重新排列時當(dāng)前拖拽元素的坐標(biāo)。

public class DragStackPanel : Panel
{
    private FrameworkElement draggingElement;
    private FrameworkElement hitElement;
    private Point mouseRelativePosition;
    private int draggingElementzIndex;
    protected override Size ArrangeOverride(Size finalSize)
    {
        double x = 0, y = 0;
        foreach (FrameworkElement child in InternalChildren)
        {
            ...

            // 獲取當(dāng)前正在拖拽元素的位置坐標(biāo)
            var dragElementPosition = GetDraggingElementMovingPosition(child);
            if (dragElementPosition != default)
            {
                // 處理拖拽元素坐標(biāo)
                var offset = new Point(dragElementPosition.X - position.X, dragElementPosition.Y - position.Y);
                child.RenderTransform = new TranslateTransform(offset.X, offset.Y);
                SetDraggingElementMovingPosition(child, dragElementPosition);
            }

            ...
        }

        return finalSize;
    }
}

運行效果

8.處理跨Panel拖拽

到目前為止已經(jīng)實現(xiàn)了本Panel內(nèi)的控件隨意拖拽換位,處理從A控件拖到B控件也類似,這里需要用到一個靜態(tài)變量來保存正在拖拽的控件,當(dāng)B控件檢測到鼠標(biāo)進(jìn)入時,只需要在A控件移除正在拖拽的控件,在B控件添加正在拖拽的控件就可以實現(xiàn)了。以下為核心代碼:

public class DragStackPanel : Panel
{
    // 通過拖拽傳遞到下一個Panel的控件
    private static FrameworkElement draggingTransferElement;
    private void Control_MouseEnter(object sender, MouseEventArgs e)
    {
        panel.Children.Remove(draggingTransferElement);
        panel.DraggingElement = null;

        Panel.SetZIndex(draggingTransferElement, this.InternalChildren.Count + 1);
        this.Children.Add(draggingTransferElement);
        this.AddOverlay(draggingTransferElement);
    }
}

以下為運行效果:

9.在ListBox、ListView、DataGrid等ItemsControl中使用拖拽功能

所有繼承自ItemsControl的控件,都有一個ItemsPanel屬性,該屬性可以指定一個Panel類型的控件來對ItemsControl進(jìn)行排列。理論上只要將ItemsControl.ItemsPanel設(shè)置為我們自己開發(fā)的Panel控件就可以實現(xiàn)排列及拖拽功能,但是這里直接使用的話并不會有效果。原因就是我們并沒有對數(shù)據(jù)綁定的情況下做處理。它的處理邏輯也與上面的類似,首先找到ItemsControl控件,通過對ItemsSource進(jìn)行操作就可以實現(xiàn)排列功能,由于代碼大同小異這里就不再贅述。以下為ListBox控件拖拽的案例效果。

<ListBox ItemsSource="{Binding Items}">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <DragStackPanel AllowCrossBorderDrag="True" CanDragAndSort="True" IsItemsHost="True"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Property1}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

10.添加動畫效果

至此基本功能已經(jīng)開發(fā)完成了,下面我們?yōu)樗砑由蟿赢嬓Ч?,讓它更具有觀賞性。動畫的核心思想就是記錄每個元素舊位置的坐標(biāo),當(dāng)元素移動到新位置時啟動一個動畫,從舊坐標(biāo)過渡到新坐標(biāo),由于代碼太過基礎(chǔ),這里就不展示了,直接上效果。

<DragStackPanel AllowCrossBorderDrag="True" CanDragAndSort="True" IsItemsHost="True">
    <DragStackPanel.ChildMoveBehavior>
        <ChildMoveBehavior Duration="0:0:0.5">
            <ChildMoveBehavior.EaseX>
                <QuinticEase EasingMode="EaseOut" />
            </ChildMoveBehavior.EaseX>
            <ChildMoveBehavior.EaseY>
                <QuinticEase EasingMode="EaseOut" />
            </ChildMoveBehavior.EaseY>
        </ChildMoveBehavior>
    </DragStackPanel.ChildMoveBehavior>
</DragStackPanel>

到此這篇關(guān)于WPF自定義Panel的示例詳解的文章就介紹到這了,更多相關(guān)WPF自定義Panel內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論