We're trying to figure out how to drag an item from a LibraryStack container onto a ScatterView, like how the photo viewer sample applications work. Currently, the item just flies back into the LibraryStack after we drag it out. We can drag and drop items into other LibraryStacks or LibraryBars.
Here's a sample of what we're trying:
<s:SurfaceWindow x:Class="Idia_seminar.SurfaceWindow1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="http://schemas.microsoft.com/surface/2008"
Title="Idia_seminar"
>
<s:SurfaceWindow.Resources>
<ImageBrush x:Key="WindowBackground" Stretch="None" Opacity="0.6" ImageSource="pack://application:,,,/Resources/WindowBackground.jpg"/>
</s:SurfaceWindow.Resources>
<Grid Background="{StaticResource WindowBackground}" >
<s:ScatterView Name="scatterView1" AllowDrop="True">
<s:SurfaceButton Name="surfaceButton1">Button</s:SurfaceButton>
<s:LibraryStack AllowDrop="True">
<s:LibraryStackItem Content="hello"></s:LibraryStackItem>
</s:LibraryStack>
</s:ScatterView>
</Grid>
</s:SurfaceWindow>
Thanks!
This is certainly doable. I cooked up an example that allows you to drag items from a librarybar located within a scatterview and drop items on the scatterview where they appear as new scatterviewitems.
I'm not sure where you went wrong, but in order for drag/drop to work, the following has to happen:
The drop target must have AllowDrop set to true
The drop target must be visible to hit testing (usually accomplished by setting a background other than null - i use Transparent)
The drop target must handle the Drop event and do something clever with the data
Here's my XAML:
<s:ScatterView AllowDrop="True" Background="Transparent"
x:Name="scatterView" s:SurfaceDragDrop.Drop="scatterView_Drop">
<s:SurfaceButton Name="surfaceButton1">Button</s:SurfaceButton>
<s:LibraryStack>
<s:LibraryStackItem Content="Hello"></s:LibraryStackItem>
</s:LibraryStack>
</s:ScatterView>
</s:ScatterView>
And in code, we handle the Drop event
private void scatterView_Drop(object sender, SurfaceDragDropEventArgs e)
{
Console.WriteLine("Got drop: " + e.Cursor.Data);
var newItem = new ScatterViewItem();
// Rely on .ToString() on the data. A real app would do something more clever
newItem.Content = e.Cursor.Data;
// Place the new item at the drop location
newItem.Center = e.Cursor.GetPosition(scatterView);
// Add it to the scatterview
scatterView.Items.Add(newItem);
}
Obviously, the code above doesn't handle dragging items back to the librarybar. I leave that as an exercise to the reader ;-)
The following MSDN guide is something I definitely think you should read: http://msdn.microsoft.com/en-us/library/ee804812.aspx
You need to set the background brush on the scatterview to a color, i.e. transparent
for it even to grab drop events.
you also need to use SurfaceDragDrop.
SurfaceDragDrop.AddDropHandler(scatterView1, OnCursorDrop);
AddHandler(ScatterViewItem.ScatterManipulationStartedEvent, new ScatterManipulationStartedEventHandler(OnManipulationStarted));
private void OnManipulationStarted(object sender, RoutedEventArgs args)
{
ScatterViewItem svi = args.OriginalSource as ScatterViewItem;
if (svi != null)// && DragDropScatterView.GetAllowDrag(svi))
{
svi.BeginDragDrop(svi.DataContext);
}
}
private void OnCursorDrop(object sender, SurfaceDragDropEventArgs args)
{
SurfaceDragCursor droppingCursor = args.Cursor;
// Add dropping Item that was from another drag source.
if (!scatterView1.Items.Contains(droppingCursor.Data)){
scatterView1.Items.Add(droppingCursor.Data);
var svi = scatterView1.ItemContainerGenerator.ContainerFromItem(droppingCursor.Data) as ScatterViewItem;
if (svi != null){
svi.Center = droppingCursor.GetPosition(scatterView1);
svi.Orientation = droppingCursor.GetOrientation(scatterView1);
svi.Height = droppingCursor.Visual.ActualHeight;
svi.Width = droppingCursor.Visual.ActualWidth;
svi.SetRelativeZIndex(RelativeScatterViewZIndex.Topmost);
}
}
}
This is all basicly from an example in the sdk, I don't remember which one of them it is sadly.
Cheers,
Stian Farstad
Related
I made a custom WPF editor and I am having trouble getting a list of screen control objects (buttons, text boxes, etc) to be displayed in the correct draw order. Here is a visual of my current setup:
I have the control object window working just fine and I can see when the list is changed.
My problem is this: Say that I selected object 3 and pressed the up button. The object should move up in the list (which it does!) BUT the draw order stays the same. Meaning that object 3 should draw behind object 2 and its not doing that. I don't know why.
Here is my code:
private void MoveUp_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < controlObjectList.Count; i++)
{
if (controlObjectList[i].IsSelected)
{
// Copy the current item
var selectedItem = controlObjectList[i];
int newindex = Math.Abs(i - 1);
// Remove the item
controlObjectList.RemoveAt(i);
// Finally add the item at the new index
controlObjectList.Insert(newindex, selectedItem);
}
}
RefreshControlObjectList(controlObjectList);
}
private void MoveDown_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < screenDesigner.m_ParentScreenObject.controlObjects.Count; i++)
{
if (controlObjectList[i].IsSelected)
{
// Copy the current item
var selectedItem = controlObjectList[i];
int newindex = i + 1;
if(newindex < controlObjectList.Count)
{
// Remove the item
controlObjectList.RemoveAt(i);
// Finally add the item at the new index
controlObjectList.Insert(newindex, selectedItem);
}
}
}
RefreshControlObjectList(controlObjectList);
}
private void RefreshControlObjectList(List<ItemsList> newList)
{
newList.Items.Clear();
foreach (ItemsList il in controlObjectList)
{
listObjects.Items.Add(il);
}
//I know this is where I should place the logic for the draw order...
}
#endregion
}
What I'm trying to figure out is how can I refresh the screen in order to see the correct draw order of my objects? Is it possible? Many thanks in advance!
BringForward and SendBackward commands can be accomplished both ways by [1] changing the order of the children or [2] by changing their ZIndex, in fact before the introduction of the ZIndex dependency properties it was only possible by changing the order of the children in the container.
Do you have a complete code sample by any chance? If not here is an example of a sample markup and code which is proving that it is indeed working, but be aware of possible performance impacts of that approach since ZIndex was introduced to fix them.
<Canvas Name="container" MouseDown="OnMouseDown">
<Ellipse Canvas.Left="50" Canvas.Top="10" Fill="Red" Height="100" Width="100"></Ellipse>
<Ellipse Canvas.Left="100" Canvas.Top="20" Fill="Green" Height="100" Width="100"></Ellipse>
<Ellipse Canvas.Left="150" Canvas.Top="30" Fill="Blue" Height="100" Width="100"></Ellipse>
</Canvas>
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
var uiElement = e.Source as Ellipse;
container.Children.Remove(uiElement);
container.Children.Add(uiElement);
}
You would need to figure out if the list of objects you are manipulating by adding and removing the children is indeed the one. It would be helpful to get some context around the variables controlObjectList and listObjects to see who they belong to.
I tried this below code:
XAML Code:
<GridView x:Name="listgrid">
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="15,15,0,0">
<Image Height="170" Width="170" Source="{Binding}"></Image>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
Cs code:
for (int i = 1; i < 50; i++)
{
list.Add("ms-appx:///Images/A-aa.jpg");
}
listgrid.ItemsSource = list;
listgrid.ScrollIntoView(listgrid.Items[30]);
I above code to scroll view to my selected item, but it's not showing any changes, i think i used this property in a wrong way any one please help me to scroll to gridview position.
I have replied your same question in MSDN: https://social.msdn.microsoft.com/Forums/windowsapps/en-US/d0a772b3-80b9-4a11-92a9-89963c29a52f/scrollintoview-property-not-working-for-gridview-in-windows-10-universal-app?forum=wpdevelop
You need to have something more to distinguish items, for example, give every image a name since items you bind to GridView are same, ScrollIntoView default find the first one.
And commonly you need to set a height property for the GridView.
For more complex requirements, there is a good thread you can reference:
Windows 10 ScrollIntoView() is not scrolling to the items in the middle of a listview
Try to subscribe on Loaded event and call ScrollIntoView inside event handler:
listgrid.Loaded += Listgrid_Loaded;
....
private void Listgrid_Loaded(object sender, RoutedEventArgs e)
{
listgrid.ScrollIntoView(listgrid.Items[30]);
}
Try this
private void Gridview_Loaded(object sender, RoutedEventArgs e)
{
if (ShellPage.Current.SelectedRecItem != null)
{
this.gridview.SelectedItem = ShellPage.Current.SelectedRecItem;
this.gridview.UpdateLayout();
this.gridview.ScrollIntoView(ShellPage.Current.SelectedRecItem);
}
}
I'm using the Items Page template from the Windows 8.1 Windows Store Apps templates in XAML. The page features a large GridView control with multiple item elements.
I would like to enable the dragging and reordering of items, but only after a user long clicks one of the items (similar to how it's done on the Windows Tablet Start menu and the iOS/Android home screen).
I've tried binding to the Holding event and enabling CanDragItems and CanReorderItems, but the user cannot start dragging the item during the Holding event.
Here's the GridView definition:
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemsGridView"
AutomationProperties.Name="Items"
TabIndex="1"
Grid.RowSpan="2"
Padding="116,136,116,46"
ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
SelectionMode="None"
IsSwipeEnabled="False"
IsItemClickEnabled="True"
CanReorderItems="False"
AllowDrop="False"
CanDragItems="False"
ItemClick="itemGridView_ItemClick"
>
With this in the code behind:
void OnHolding(object sender, HoldingRoutedEventArgs e)
{
if( e.HoldingState == Windows.UI.Input.HoldingState.Started)
{
Debug.WriteLine("Drag Start");
itemGridView.CanDragItems = true;
itemGridView.IsSwipeEnabled = true;
itemGridView.CanReorderItems = true;
itemGridView.AllowDrop = true;
}
else
{
Debug.WriteLine("Drag End");
itemGridView.CanDragItems = false;
itemGridView.IsSwipeEnabled = false;
itemGridView.CanReorderItems = false;
itemGridView.AllowDrop = false;
}
}
Thanks!
After much fuss and chasing down events, I was able to get the intended effect on pen, mouse, and touch devices.
The following code is not guaranteed to be the best way to accomplish the long click drag, but it is functioning on my devices with Windows 8.1. I encourage someone to find a better solution, since this one is kind of messy.
Code behind looks like this:
private bool isHolding = false;
private bool canUserDragItem = false;
// If a user moves their pointer outside the item's area or releases their pointer, stop all holding/dragging actions.
private void Grid_StopAllowDrag(object sender, PointerRoutedEventArgs e)
{
canUserDragItem = false;
isHolding = false;
}
// If a user starts dragging an item, check and see if they are holding the item first.
private void itemGridView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
{
if (!canUserDragItem) e.Cancel = true;
}
private async void Grid_PointerPressed(object sender, PointerRoutedEventArgs e)
{
// Whenever a user presses the pointer inside the item, wait for half a second, then decide if the user is holding the item.
isHolding = true;
await Task.Delay(500); // Wait for some amount of time before allowing them to drag
if (isHolding) // If the user is still holding, allow them to drag the item.
{
canUserDragItem = true; // Allow them to drag now
// TODO: Make it apparent that the user is able to drag the item now.
}
}
And the XAML looks like this:
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemsGridView"
AutomationProperties.Name="Items"
TabIndex="1"
Grid.RowSpan="2"
Padding="116,136,116,46"
ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
SelectionMode="None"
IsSwipeEnabled="True" <!-- Enable dragging on touch devices -->
CanReorderItems="True" <!-- Allow users to try to start dragging -->
AllowDrop="True"
CanDragItems="True"
DragItemsStarting="itemGridView_DragItemsStarting" <!-- Stop dragging while not holding -->
>
<GridView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Left" Width="250" Height="250"
<!-- Items must be given these event handlers -->
PointerPressed="Grid_PointerPressed"
PointerReleased="Grid_StopAllowDrag"
PointerCanceled="Grid_StopAllowDrag"
PointerCaptureLost="Grid_StopAllowDrag"
PointerExited="Grid_StopAllowDrag"
>
You need another else case. Your logic looks like this.
If they started doing something. Set properties to true.
While they are still holding the properties will be set to false.
You probably want two separate events. Holding started and Holding stopped. Not just a blanket else there.
I have a text box that will retain the last 10 entries entered, similar to the search box in Internet Explorer. The user can click on the dropdown menu to see last 10 entries. The drop down menu is a combo box. I created an Observable collection of strings that is bound to the combo box Itemssource.Below is the code.
Xaml
<Grid x:Name="TextBox_grid" Margin="0,0,40,0" Width="360" Height="23">
<ComboBox Name="cb" Margin="0,0,-29,0" Style="{DynamicResource Onyx_Combo}" ItemsSource="{Binding TextEntries, ElementName=TheMainWindow, Mode=OneWay}" IsEditable="False" Visibility="Visible" />
<Rectangle Fill="#FF131210" Stroke="Black" RadiusX="2" RadiusY="2"/>
<TextBox Name=UniversalTextBox Margin="0" Background="{x:Null}" BorderBrush="{x:Null}" FontSize="16" Foreground="#FFA0A0A0" TextWrapping="Wrap" PreviewKeyDown="TextBox_PreviewKeyDown"/>
</Grid>
Code
public partial class Window1 : Window
{
private ObservableCollection<string> m_TextEntries = new ObservableCollection<string>();
public Window1()
{
InitializeComponent();
}
public ObservableCollection<string> TextEntries
{
get { return m_TextEntries; }
}
private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
TextBox textBox = sender as TextBox;
if (textBox == null)
return;
if (e.Key == Key.Enter)
{
PopulateHistoryList(textBox.Text);
e.Handled = true;
}
if (e.Key == Key.Escape)
{
e.Handled = true;
}
}
private void PopulateHistoryList(string text)
{
m_TextEntries.Add(text);
}
private event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
The above code will populate the TextEntries collection when the Enter Key is pressed on the textbox. I need two things
How do I set the Selected Item of the combo box and how can I bind that to my text box.
The combobox(dropmenu) should only show the last 10 entries from the drop down menu.
Thanks in advance,
Using Expesssion Blend, binding the value of a property of one control to the property value of another control is easy, and it is known as ElementProperty Binding, here is a screenshot of where you access the ability to create this within Blend, note that the Textbox is the selected element in the Objects and Timeline panel, and it's the 'little box' to the right of the Text property in the properties panel which has been clicked to produce the context menu pictured...
Once you have selected 'Element Property Binding' for the text property of your textbox, your cursor will become a little bullseye icon, which you now will use to indicate what you want to bind to, by clicking it in either the design canvas or the Objects and Timeline panel, while the cursor appears that way...
Here we see the 'SelectedValue' property of the combo box being selected as the source of what is displayed in the textbox. Once done, the textbox will automagically be immediately set to display whatever is selected in the combo. Be sure to take a look at what Blend is doing in your XAML when you do this, as it will help you better understand what is actually going on, and might even teach you a thing or two about the binding syntax of XAML.
As for the list only ever having the last ten entries...there are several ways to do this, each one more or less appropriate, depending on the surrounding context, but here is one way; simply run a procedure similar to this one, whenever the box has entries added to it:
// assuming 'listItems' is your ObservableCollection
string[] items = listItems.ToArray();
// prepare a new array for the current ten
string[] tenItems = new string[10];
// copy a subset of length ten, to the temp array, the set your ObservableCollection to this array.
Array.Copy(items, (items.Length - 10), tenItems, 0, 10);
Note: The Array .Copy assumes the only way items are getting added to the observable collection is by some form of .Add, which always adds them to the end of the list...
Part of the answer
<TextBlock Text="{Binding ElementName=cb, Path=SelectedValue}" />
<ComboBox x:Name="cb" ItemsSource="{Binding Path=Fields}" SelectedValue="{Binding Path=SelectedValue}" />
And if you set the datacontext of the window
DataContext="{Binding RelativeSource={RelativeSource self}}">
Is it possible to implement mouse click and drag selection box in WPF. Should it be done through simply drawing a rectangle, calculating coordinates of its points and evaluating position of other objects inside this box? Or are there some other ways?
Could you give a bit of sample code or a link?
Here is sample code for a simple technique that I have used in the past to draw a drag selection box.
XAML:
<Window x:Class="DragSelectionBox.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
>
<Grid
x:Name="theGrid"
MouseDown="Grid_MouseDown"
MouseUp="Grid_MouseUp"
MouseMove="Grid_MouseMove"
Background="Transparent"
>
<Canvas>
<!-- This canvas contains elements that are to be selected -->
</Canvas>
<Canvas>
<!-- This canvas is overlaid over the previous canvas and is used to
place the rectangle that implements the drag selection box. -->
<Rectangle
x:Name="selectionBox"
Visibility="Collapsed"
Stroke="Black"
StrokeThickness="1"
/>
</Canvas>
</Grid>
</Window>
C#:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
bool mouseDown = false; // Set to 'true' when mouse is held down.
Point mouseDownPos; // The point where the mouse button was clicked down.
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
// Capture and track the mouse.
mouseDown = true;
mouseDownPos = e.GetPosition(theGrid);
theGrid.CaptureMouse();
// Initial placement of the drag selection box.
Canvas.SetLeft(selectionBox, mouseDownPos.X);
Canvas.SetTop(selectionBox, mouseDownPos.Y);
selectionBox.Width = 0;
selectionBox.Height = 0;
// Make the drag selection box visible.
selectionBox.Visibility = Visibility.Visible;
}
private void Grid_MouseUp(object sender, MouseButtonEventArgs e)
{
// Release the mouse capture and stop tracking it.
mouseDown = false;
theGrid.ReleaseMouseCapture();
// Hide the drag selection box.
selectionBox.Visibility = Visibility.Collapsed;
Point mouseUpPos = e.GetPosition(theGrid);
// TODO:
//
// The mouse has been released, check to see if any of the items
// in the other canvas are contained within mouseDownPos and
// mouseUpPos, for any that are, select them!
//
}
private void Grid_MouseMove(object sender, MouseEventArgs e)
{
if (mouseDown)
{
// When the mouse is held down, reposition the drag selection box.
Point mousePos = e.GetPosition(theGrid);
if (mouseDownPos.X < mousePos.X)
{
Canvas.SetLeft(selectionBox, mouseDownPos.X);
selectionBox.Width = mousePos.X - mouseDownPos.X;
}
else
{
Canvas.SetLeft(selectionBox, mousePos.X);
selectionBox.Width = mouseDownPos.X - mousePos.X;
}
if (mouseDownPos.Y < mousePos.Y)
{
Canvas.SetTop(selectionBox, mouseDownPos.Y);
selectionBox.Height = mousePos.Y - mouseDownPos.Y;
}
else
{
Canvas.SetTop(selectionBox, mousePos.Y);
selectionBox.Height = mouseDownPos.Y - mousePos.Y;
}
}
}
}
I wrote an article about this:
https://www.codeproject.com/Articles/148503/Simple-Drag-Selection-in-WPF
You can get this functionality pretty easily by adding an InkCanvas and set its EditingMode to Select. Although it's primarily intended for Tablet PC ink collection and rendering, it's very easy to use it as a basic designer surface.
<Window Width="640" Height="480" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<InkCanvas EditingMode="Select">
<Button Content="Button" Width="75" Height="25"/>
<Button Content="Button" Width="75" Height="25"/>
</InkCanvas>
</Window>
This project created a custom MultiSelector which supports several selection methods including a rectangular "lasso" style:
Developing a MultiSelector by Teofil Cobzaru
It is far too long to reproduce here. The key elements of the design, IIRC, were to create a custom ItemContainer which knows how to interact with its MultiSelector parent. This is analagous to ListBoxItem / ListBox.
This is probably not the simplest possible approach, however if you are already using some type of ItemsControl to host the items which may need to be selected, it could fit into that design pretty easily.
MouseDown logic:
MouseRect.X = mousePos.X >= MouseStart.X ? MouseStart.X : mousePos.X;
MouseRect.Y = mousePos.Y >= MouseStart.Y ? MouseStart.Y : mousePos.Y;
MouseRect.Width = Math.Abs(mousePos.X - MouseStart.X);
MouseRect.Height = Math.Abs(mousePos.Y - MouseStart.Y);