First things first: I have a WPF project which uses C# and MVVM (MVVM Light), in Visual Studio 2010.
I have a Canvas control inside which is a ListBox - each ListBoxItem can be moved around with mouse drag (each has a DataTemplate contains a Thumb control to allow each item to be dragged).
That looks as follows:
<DataTemplate>
<Grid Background="Transparent">
<Thumb Name="myThumb" Template="{StaticResource NodeVisualTemplateRegular}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="DragDelta">
<cmd:EventToCommand Command="{Binding ChatNodeListViewModel.DragDeltaCommand, Source={StaticResource Locator}}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Thumb>
</Grid> </DataTemplate>
As you can see, there's a command which handles the dragging part (DeltaDragCommand).
None of this is a problem, but then I wanted to add the ability to pan around the Canvas control with a click and drag motion. This is where things come into conflict with the above stated dragging of ListBoxItems.
The Canvas pan stuff is handled in the code behind and the subscriptions for the events look like this:
NodeDragScrollViewer.PreviewMouseLeftButtonDown += OnMouseLeftButtonDown;
NodeDragScrollViewer.PreviewMouseLeftButtonUp += OnMouseLeftButtonUp;
NodeDragScrollViewer.MouseMove += OnMouseMove;
NodeDragScrollViewer.PreviewMouseWheel += OnPreviewMouseWheel;
NodeDragScrollViewer.ScrollChanged += OnScrollViewerScrollChanged;
The events we see here are run on the ScrollViewer (a control in which the Canvas control sits). The problem is that these events sit 'on top of' the ListBoxItems - the click for the pan of the canvas will activate before the click for the drag of the ListBoxItem.
Now a solution I've seen is to use the VisualTreeHelper HitTest method to find out what was clicked on. If I can find out if a ListBoxItem was clicked on, then I can ignore the Canvas pan actions and let things progress as before.
The problem I'm facing is that I just can't get this to work. I have seem people explain how this should work and have borrowed code for this action. I present that code here:
Point pt = new Point();
VisualTreeHelper.HitTest(NodeDragCanvas, null,
new HitTestResultCallback(MyHitTestResult),
new PointHitTestParameters(pt));
private HitTestResultBehavior MyHitTestResult(HitTestResult result)
{
var p = FindParent<ListBoxItem>(result.VisualHit);
//Set the behavior to return visuals at all z-order levels.
return HitTestResultBehavior.Continue;
}
public static T FindParent<T>(DependencyObject child) where T : DependencyObject
{
//get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
return parent;
else
return FindParent<T>(parentObject);
}
If you look at the contents of HitTestResultBehaviour, this is where I check for the control that I've clicked on. I'm hoping it will be a ListBoxItem... but that always returns 'null'. I am aware that the method can fire multiple times, but I've never seen the var p (in this case) be anything other than a null value. If, however, I attempt to use FindParent<ListBox>, I will get returned the ListBox. It has the right amount of items in it and seems to be as I would expect. But this doesn't work for me since my ListBox is the size of the Canvas.
It seems this is the key point, but I don't know what else to try and all avenues of research seem to lead back to this method.
Can anyone offer any guidance?
Thanks
Yes, obviously you need to use the PreviewXXX events on the Canvas, or you wouldn't get the mouse events for the ListBoxItems in the Canvas because the ListBoxItem already ate them.
You might want to consider restructuring your code so that instead of using a ListBox, you use a MyListBox (or whatever you want to call it) and trap the ListBoxItem mouse events there and handle just the Canvas dragging in the Canvas. The way you have it now, you are intermingling both of those cases when they are really completely separate. The canvas should handle its thing and the list box should handle its thing.
If you want to keep the code how you have it now, take a look at the MouseEventArgs, original source, sender, etc. One of those might have the original sender, but it'll probably be a TextBlock rather then a ListBoxItem since that's the top level item, so...
Google around for a well known class VisualTreeExtensions and use that. When you get the TextBlock or whatever, you can just do something like ((DependencyObject)e.OriginalSource).GetVisualAncestor<ListBoxItem>() and if it's not null, you know you're inside of a ListBox.
Related
I'm having what's probabily a stupid doubt, but I really don't know how to fix this thing.
I have a complex UI with many items inside it (a SplitView with other stuff inside it, then a Frame which holds a Page, where I have a Grid and finally my CommandBar).
When I tap on the "..." button, the CommandBar opens towards the top of the window, and it also actually covers part of the UI outside of the Frame where its parent Page is located (I don't event know how's that possible).
I tried setting the VerticalAlignment property of both the CommandBar, the parent Grid, the Page etc... to Top, but the CommandBar still opens towards the top of the screen.
Is there a way to have it open towards the bottom, just like the CommandBar in the built in Weather or Photo app? Am I missing a setting/property here?
Thank you for your help!
Sergio
Edit: just to be clear, the basic structure for my page is something like this:
RootContent > ... > Frame > Page > Grid > CommandBar
I can't put the CommandBar inside the Page.TopAppBar as if I do that the CommandBar gets placed outside of my Page and covers the top of my UI. I need the CommandBar to be place inside the Page.
The CommandBar relies on VisualStates to control this part of its dimensions and animations this makes it easy to use a custom visual state manager to intercept the state change calls and replace all OpenUp calls with OpenDown ones.
public class OpenDownCommandBarVisualStateManager : VisualStateManager
{
protected override bool GoToStateCore(Control control, FrameworkElement templateRoot, string stateName, VisualStateGroup group, VisualState state, bool useTransitions)
{
//replace OpenUp state change with OpenDown one and continue as normal
if(!string.IsNullOrWhiteSpace(stateName) && stateName.EndsWith("OpenUp"))
{
stateName = stateName.Substring(0, stateName.Length - 6) + "OpenDown";
}
return base.GoToStateCore(control, templateRoot, stateName, group, state, useTransitions);
}
}
public class OpenDownCommandBar : CommandBar
{
public OpenDownCommandBar()
{
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
var layoutRoot = GetTemplateChild("LayoutRoot") as Grid;
if(layoutRoot != null)
{
VisualStateManager.SetCustomVisualStateManager(layoutRoot, new OpenDownCommandBarVisualStateManager());
}
}
}
Then just use the new OpenDownCommandBar instead of the normal one.
<myControls:OpenDownCommandBar>...</myControls:OpenDownCommandBar>
How are you defining your command bar? There are two ways to do so:
<Page.BottomAppBar>
<CommandBar>
....
</CommandBar>
</Page.BottomAppBar>
Or on the contrary..
<Page.TopAppBar>
<CommandBar>
....
</CommandBar>
</Page.TopAppBar>
In your case you would want to use the former and make sure your appbar is not inside any other containers.
You're using the commandbar for something it wasn't intended for. It's supposed to cover page content when displayed.
If you can't change the way something behaves (as in this case) then the alternative would be a different control (there isn't anything I'm aware of which would give you exactly what you want) or make your own.
I am working with mediaelement in wpf, but the problem is I can't drop media on medaiaelement.
can anyone tell me the solution.the following code is .cs file code. I set allow drop property= true
private void mediaElement1_Drop(object sender, System.Windows.DragEventArgs e)
{
String[] FileName = (String[])e.Data.GetData(System.Windows.Forms.DataFormats.FileDrop, true);
if (FileName.Length > 0)
{
String VideoPath = FileName[0].ToString();
mediaElement1.Source = new Uri(VideoPath);
mediaElement1.Play();
}
e.Handled = true;
}
Tried it myself. Actually it will work after you play something there.
Here's the point: assume we have a Grid:
<Grid AllowDrop="True"></Grid>
It won't allow drop.
Now the following
<Grid Background="Transparent" AllowDrop="True"></Grid>
Will allow drop.
The first Grid doesn't have background at all, so actually there's no way to drop anything on it - there's no grid. And in second case there is grid's background even though we can't see it.
The same thing applies to MediaElement. Unfortunately it doesn't have any Background or Content property, so it won't allow drop until you start playing something there.
Solution is to handle drop on MediaElement parent container.
By the way, don't forget to set LoadedBehavior="Manual" for MediaElement so that it will play dropped file.
EDIT.
Here is explanation why MediaElement doesn't allow drop till any content was loaded in it.
Every WPF component is in fact composed of some other basic elements: Borders, Grids, ContentPresenters etc. So something inside the MediaElement handles drop. I cannot tell you what element it is because MediaElement's Template is not accessible. But it really doesn't matter what exactly is the element that handles drag and drop there. What does matter is that there's is nothing material in MediaElement's area until you load content on it - just like in case of my example with Grid at the beginning of this post. I mean that when you move mouse cursor over it's area there is nothing between cursor and MediaElement's container. Try to handle MouseDown event: result will be the same - it won't fire until you load any video. Why? Because there is nothing to raise event. Nothing cannot raise anything.
As I mentioned before there is great difference between Background="{x:Null}" and Background="Transparent": in first case there's no background brush, no background, but in second case there is one. Feel the difference.
On the main page of my app I have a grid with 6 x 4 columns and rows of buttons.
I want to move one of these buttons to the middle and then scale it larger using RadControls by Telerik.
I can do this easily however when I do the button is shown behind all the other buttons on the grid and I can't seem to make it come to the front.
Any help would be much appreciated.
The order that items were added to a panel/grid determines what control is above another. Looking around it looks like you have two options:
Change the Canvas.ZIndex for the button you want to be on top.
Yes it seems odd as there is no canvas, but it works for any panel or grid.
Remove and re-add the child from the parent grid so that it was last. I found a nice snippet of code here posted by "CleverCoder" : http://forums.silverlight.net/post/63607.aspx
//Originally posted by CleverCode - http://forums.silverlight.net/post/63607.aspx
public static void PushToTop(this FrameworkElement element)
{
if (element == null) throw new ArgumentNullException("element");
var parentPanel = element.Parent as Panel;
if (parentPanel != null)
{
// relocate the framework element to be the last in the list (which makes it "above" everything else)
parentPanel.Children.Remove(element);
parentPanel.Children.Add(element);
parentPanel.UpdateLayout();
}
}
There are lots of tips around Drag and Drop on the Windows Phone, but I am currently unable to put everything together. So I hope you can give me some advice to reach my goal: Display a scrollable list of items with good reordering and scroll experience.
I use a StackPanel to present a vertical list of controls. Let's say these controls are CheckBoxes displaying some information (in reality I created a bit more complex custom controls). There can be lots of items so I put a ScrollViewer around the StackPanel so the user can scroll up and down. But now I also want to give the user the opportunity to reorder the controls in the list via Drag and Drop.
Several things are unclear for me:
How do I enable Drag and Drop functionality in the StackPanel? (So it looks smooth and the items change position in an animated, nice to look at, way; they should keep making space for the item-to-be-inserted while the user drags it around.)
How can I achieve that the user can vertically scroll the list while still being able to Drag and Drop items? (I think there could be a special "drag spot" on every item the user has to drag at, so I can differentiate between dragging and scrolling.)
How do I auto-scroll the list when the user drags one item to the upper or lower border if the list is bigger than the screen?
Is this even the right combination of controls? Is there a better one? (But I don't want to calculate item positions manually.)
I'd love to hear your ideas on this topic, any help is greatly appreciated!
Hi you could check this out, might be what you're looking for:
http://blogs.msdn.com/b/jasongin/archive/2011/01/03/wp7-reorderlistbox-improvements-rearrange-animations-and-more.aspx
You can refer this link. This has a nice reordering of listbox with vertical scrolling. Hold the item to be dragged for 1 min and start dragging.
The answer you seek is the ReorderListBox control developed by Jason Ginchereau.
I'm going to show a quick implementation of it, but if you want a complete demo, then download the source from CodePlex here.
First, install the control from Nuget:
Tools >>> Library Package Manager >>> Manage NuGet Packages for Solution...
Search for ReorderListBox, and install the one created by Jason Ginchereau
Then, in the XAML of your app's start page (ie. MainPage.xaml), copy and paste the highlighted assembly reference into the phone:PhoneApplicationPage tag at the top where the other assembly references are located.
xmlns:rlb="clr-namespace:ReorderListBox;assembly=ReorderListBox"
Next, drop this into your XAML page
<rlb:ReorderListBox
x:Name="reorderListBox"
Grid.Row="2"
Margin="12,0,12,12"
IsReorderEnabled="True">
<rlb:ReorderListBox.ItemTemplate>
<DataTemplate>
<TextBlock
Margin="12,4,12,4"
FontSize="36"
Text="{Binding}" />
</DataTemplate>
</rlb:ReorderListBox.ItemTemplate>
</rlb:ReorderListBox>
Finally, in your code-behind (ie MainPage.xaml.cs), you want to define an ObservableCollection with your list of data and assign it to the reorderListBox.ItemsSource. You may also want to save the state of the list after it has been resorted for the next time the application is opened. Here's an example:
public partial class MainPage : PhoneApplicationPage
{
public ObservableCollection<string> SampleDataList { get; set; }
// Constructor
public MainPage()
{
InitializeComponent();
if (IsolatedStorageSettings.ApplicationSettings.Contains("SampleDataList"))
{
SampleDataList = IsolatedStorageSettings.ApplicationSettings["SampleDataList"] as ObservableCollection<string>;
}
else
{
SampleDataList = new ObservableCollection<string>();
SampleDataList.Add("Zero");
SampleDataList.Add("One");
SampleDataList.Add("Two");
SampleDataList.Add("Three");
SampleDataList.Add("Four");
SampleDataList.Add("Five");
SampleDataList.Add("Six");
SampleDataList.Add("Seven");
SampleDataList.Add("Eight");
SampleDataList.Add("Nine");
SampleDataList.Add("Ten");
SampleDataList.Add("Eleven");
SampleDataList.Add("Twelve");
SampleDataList.Add("Thirteen");
SampleDataList.Add("Fourteen");
}
reorderListBox.ItemsSource = SampleDataList;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
IsolatedStorageSettings.ApplicationSettings["SampleDataList"] = SampleDataList;
IsolatedStorageSettings.ApplicationSettings.Save();
}
}
I have a Listbox displaying 5 items at a time, horizontally. I want to implement a 'Previous' and 'Next' button that when clicked, will display the previous set of 5, or next set of 5 items. I am not displaying the horizontal scroll bar, as I don't want a user to navigate with that.
Is there already some functionality implemented so I can do this, or do I need to write code to calculate what items I'm displaying, etc, in the button's click events?
Thanks in advance!
You can use the built-in ScrollViewer.PageUp() and ScrollViewer.PageDown() commands, like this:
public void ShowNextPage()
{
InvokeOnScrollViewer(listBox, viewer => viewer.PageDown());
}
public void ShowPriorPage()
{
InvokeOnScrollViewer(listBox, viewer => viewer.PageUp());
}
public void InvokeOnScrollViewer(ItemsControl control, Action<ScrollViewer> action)
{
for(Visual vis = control as Visual; VisualTreeHelper.GetChildCount(vis)!=0; vis = VisualTreeHelper.GetChild(vis, 0))
if(vis is ScrollViewer)
{
Action((ScrollViewer)vis);
break;
}
}
How it works: InvokeOnScrollViewer scans down the visual tree until it finds the ScrollViewer, then invokes the given action on it, which is either PageUp() or PageDown().
When your ItemsPanel is a StackPanel (of either orientation, virtualizing or not), ScrollViewer.PageUp() moves back by one viewport and ScrollViewer.PageDown() moves forward by one viewport. In other words, if your ListBox shows five items then these commands move it by five items.
Look at ListBox.ScrollIntoView() method.