简体   繁体   中英

Dynamically Create Draggable UserControl in Win8 Metro App

At the minute I have a view which populates a ListView with tiles bound to a list of users. OnClick of any of these Tiles(buttons) I need to dynamically create a small draggable window consisting of a StackPanel containing an ScrollViewer&ItemsControl, Textbox and Button. This will then have to be bound to an ObservableCollection based on which user Tile was clicked.

This will be used in a private chat scenario.

I have already implemented a group chat bound to an ObservableCollection but this is created on navigation to the page.

I have started by adding the same set of controls to a dataTemplate to Resources.xaml but am quite lost as to where to go next.

<DataTemplate x:Key="PrivateChatTemplate">
    <StackPanel Width="267" Height="300" >
        <ScrollViewer x:Name="PrivateScrollViewer" Grid.ColumnSpan="2" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" >
            <ItemsControl Name="PrivateItemsControl" Foreground="Black" />
        </ScrollViewer>
        <TextBox x:Name="PrivateTextBox" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Top" Width="201" Height="60" BorderThickness="1" BorderBrush="Black"/>
        <Button x:Name="PrivateSendButton" Content="Send" HorizontalAlignment="Left" Height="58" Margin="65,2,0,0" VerticalAlignment="Top" Width="66"   Click="PrivateSendButton_Click" Background="Black"/>
    </StackPanel>       
</DataTemplate>

Thanks for any help.

Funny you mentioned a ScrollViewer since that gave me an idea for using a ScrollViewer to position a foreground window-like control in front of other content and it's fairly simple.

Inside your page - put a ScrollViewer that extends to the full size of the app window (by setting both VerticalAlignment and HorizontalAlignment to Stretch ), that has a Panel like a Canvas or Grid inside of it and place the window/ UserControl inside of it - like the Rectangle in the code below. Make sure the ScrollViewer can scroll both ways by setting the -ScrollMode/-ScrollBarVisibility values and the size of the panel to be larger than the ScrollViewer's ViewportWidth and ViewportHeight . You should handle SizeChanged event on the ScrollViewer and the window inside of it and set the panel's Width and Height to something like

panel.Width = scrollViewer.ViewportWidth * 2 - window.ActualWidth;
panel.Height = scrollViewer.ViewportHeight * 2 - window.ActualWidth;

Now everything should become scrollable by touch. The remaining problem is handling mouse input which you can do based on Pointer- events on the window.

XAML

<Page
    x:Class="DraggableWindow.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DraggableWindow"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Button
            Content="Button"
            HorizontalAlignment="Left"
            Height="99"
            Margin="112,101,0,0"
            VerticalAlignment="Top"
            Width="119" />
        <Button
            Content="Button"
            HorizontalAlignment="Left"
            Height="147"
            Margin="985,389,0,0"
            VerticalAlignment="Top"
            Width="262" />
        <Button
            Content="Button"
            HorizontalAlignment="Left"
            Height="147"
            Margin="403,581,0,0"
            VerticalAlignment="Top"
            Width="262" />
        <Button
            Content="Button"
            HorizontalAlignment="Left"
            Height="147"
            Margin="112,277,0,0"
            VerticalAlignment="Top"
            Width="262" />
        <Button
            Content="Button"
            HorizontalAlignment="Left"
            Height="147"
            Margin="682,129,0,0"
            VerticalAlignment="Top"
            Width="262" />
        <Button
            Content="Button"
            HorizontalAlignment="Left"
            Height="147"
            Margin="551,371,0,0"
            VerticalAlignment="Top"
            Width="262" />
        <ScrollViewer
            x:Name="scrollViewer"
            SizeChanged="OnScrollViewerSizeChanged"
            Background="{x:Null}"
            HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch"
            HorizontalScrollMode="Auto"
            HorizontalScrollBarVisibility="Hidden"
            VerticalScrollBarVisibility="Hidden"
            IsHorizontalRailEnabled="False"
            IsVerticalRailEnabled="False">
            <Canvas
                x:Name="panel">
                <Rectangle
                    x:Name="window"
                    SizeChanged="OnWindowSizeChanged"
                    PointerPressed="OnWindowPointerPressed"
                    PointerMoved="OnWindowPointerMoved"
                    PointerReleased="OnWindowPointerReleased"
                    Fill="LightGray"
                    Width="200"
                    Height="150"/>
            </Canvas>
        </ScrollViewer>
    </Grid>
</Page>

C# using Windows.Devices.Input; using Windows.Foundation; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Input;

namespace DraggableWindow
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }

        private uint pointerId;
        private Point lastPoint;

        private void OnWindowPointerPressed(object sender, PointerRoutedEventArgs e)
        {
            if (e.Pointer.PointerDeviceType == PointerDeviceType.Mouse)
            {
                window.CapturePointer(e.Pointer);
                this.pointerId = e.Pointer.PointerId;
                this.lastPoint = e.GetCurrentPoint(window).Position;
            }
        }

        private void OnWindowPointerMoved(object sender, PointerRoutedEventArgs e)
        {
            if (e.Pointer.IsInContact &&
                e.Pointer.PointerId == pointerId)
            {
                var point = e.GetCurrentPoint(window).Position;
                this.scrollViewer.ChangeView(
                    this.scrollViewer.HorizontalOffset - point.X + lastPoint.X,
                    this.scrollViewer.VerticalOffset - point.Y + lastPoint.Y,
                    null,
                    true);
            }
        }

        private void OnWindowPointerReleased(object sender, PointerRoutedEventArgs e)
        {
            if (e.Pointer.PointerId == pointerId)
            {
                window.ReleasePointerCapture(e.Pointer);
            }
        }

        private void OnScrollViewerSizeChanged(object sender, SizeChangedEventArgs e)
        {
            UpdateWindowingLayout();
        }

        private void OnWindowSizeChanged(object sender, SizeChangedEventArgs e)
        {
            UpdateWindowingLayout();
        }

        private void UpdateWindowingLayout()
        {
            this.panel.Width = this.scrollViewer.ViewportWidth * 2 - 0.0 * this.window.ActualWidth;
            this.panel.Height = this.scrollViewer.ViewportHeight * 2 - 0.5 * this.window.ActualHeight;
            Canvas.SetLeft(this.window, this.scrollViewer.ViewportWidth - 0.5 * this.window.ActualWidth);
            Canvas.SetTop(this.window, this.scrollViewer.ViewportHeight - 0.5 * this.window.ActualHeight);
        }
    }
}

Oh and to make it all dynamic - wrap it in a UserControl to handle the events there and put that in a Popup . I'll see about wrapping all that in a reusable control when I get a chance, since I need something like that too for my visual tree debugger overlay.

@Filip Skakun Unfortunately I have to use 8.0 as opposed to 8.1, so don't have the new ChangeView method. I have attempted to make a custom UserControl but I'm not sure how to handle the SizeChanged and UpdateWindowingLayout since the windows will be created on click of a button essentially and dynamically created. I then need to bind a list of strings to the ItemsControl inside the UserControl.
`Unfortunately I have to use 8.0 as opposed to 8.1, so don't have the new ChangeView method. I have attempted to make a custom UserControl but I'm not sure how to handle the SizeChanged and UpdateWindowingLayout(to implement the dragging) since the windows will be created on click of a button essentially and dynamically created. I then need to bind a list of strings to the ItemsControl inside the UserControl. '

    <UserControl
x:Class="KeyOui.View.PrivateChatWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:KeyOui.View"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="200"
d:DesignWidth="300">

<StackPanel Background="Indigo">
    <ScrollViewer>
        <ItemsControl Name="PrivateChatItemsControl" ItemsSource="{Binding ListOfMessages.Name}" Width="Auto" Height="150" Foreground="Black" BorderBrush="Gray" BorderThickness="2" />
    </ScrollViewer>
    <StackPanel VerticalAlignment="Stretch" Orientation="Horizontal" HorizontalAlignment="Stretch">
        <TextBox x:Name="GroupChatTextBox" VerticalAlignment="Bottom" TextWrapping="Wrap" FontSize="14" Width="140" Height="40" Margin="5,5,5,5" BorderThickness="1" BorderBrush="Gray"/>
        <Button x:Name="SendButton" Content="Send"  
                        HorizontalAlignment="Left" Height="40" 
                        VerticalAlignment="Top" Width="Auto"  
                        Click="SendButton_Click"  Margin="5,5,5,5"
                        BorderThickness="1" BorderBrush="Gray"/>
    </StackPanel>
</StackPanel>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM