Conal
Conal

Reputation: 33

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.

Upvotes: 0

Views: 580

Answers (2)

Conal
Conal

Reputation: 33

@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>

Upvotes: 0

Filip Skakun
Filip Skakun

Reputation: 31724

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.

Upvotes: 1

Related Questions