I'm using WPF to create two ListViews and implement drag-drop functionality. (both intra-listview and inter-listview)
I found an interesting post here which does that.
However, there is a problem. When I drag a listviewitem from listView1, I see the adorner (the ghost image) only inside listView1. When I want to drop the listviewItem on ListView2, I must see the adorner there as well. Basically, the adorner appears only on the listView from which the drag operation started. Once it is outside the listView, it disappears.
I have done a bit of research and could not find a way to make the adorner visible outside the control from which the drag was initiated.
Can anyone help me with some suggestions?
Wire up the GiveFeedback event to update the adorner location beyond the listview. Updated ListView property from the example and method below (and in the listview_DragLeave method you won't want to collapse the adorner):
/// <summary>
/// Gets/sets the ListView whose dragging is managed. This property
/// can be set to null, to prevent drag management from occuring. If
/// the ListView's AllowDrop property is false, it will be set to true.
/// </summary>
public ListView ListView
{
get { return listView; }
set
{
if( this.IsDragInProgress )
throw new InvalidOperationException( "Cannot set the ListView property during a drag operation." );
if( this.listView != null )
{
#region Unhook Events
this.listView.PreviewMouseLeftButtonDown -= listView_PreviewMouseLeftButtonDown;
this.listView.PreviewMouseMove -= listView_PreviewMouseMove;
this.listView.DragOver -= listView_DragOver;
this.listView.DragLeave -= listView_DragLeave;
this.listView.DragEnter -= listView_DragEnter;
this.listView.GiveFeedback -= listView_GiveFeedback;
this.listView.Drop -= listView_Drop;
#endregion // Unhook Events
}
this.listView = value;
if( this.listView != null )
{
if( !this.listView.AllowDrop )
this.listView.AllowDrop = true;
#region Hook Events
this.listView.PreviewMouseLeftButtonDown += listView_PreviewMouseLeftButtonDown;
this.listView.PreviewMouseMove += listView_PreviewMouseMove;
this.listView.DragOver += listView_DragOver;
this.listView.DragLeave += listView_DragLeave;
this.listView.DragEnter += listView_DragEnter;
this.listView.GiveFeedback += listView_GiveFeedback;
this.listView.Drop += listView_Drop;
#endregion // Hook Events
}
}
}
void listView_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
if (this.ShowDragAdornerResolved)
this.UpdateDragAdornerLocation();
}
Related
In my WPF application I have one datagrid and one textbox. In the textChanged event of the textbox, I put this:
myDatagrid.ItemsSource =
myListOfObjects.Where(item => item.Name.Contains(MyTextBox.Text)); //Filter
if (myDatagrid.Items.Count > 0) // If no itens, then do nothing
{
myDatagrid.SelectedIndex = 0; // If has at least one item, select the first
}
myDatagrid.Items.Refresh();
Note that I force the selection when the text changes, in the first row of the DataGrid.
But unfortunately, the color of the row does not change to blue, making it hard to see the selection.
I realy need this, because in the PreviewKeyDown event of the textbox I have this:
private void myTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Up)
{
if (!(myDataGrid.SelectedIndex <= 0))
{
myDataGrid.SelectedIndex--; // Go one position Up
}
}
if (e.Key == Key.Down)
{
if (!(myDataGrid.SelectedIndex == myDataGrid.Items.Count - 1))
{
myDataGrid.SelectedIndex++; // Go one position Down
}
}
}
So, when the textbox is focused and the user press the Up or the Down key, the selection does not appear to change.
Any idea of how I can make the selected item on the datagrid change it's color to blue?
Other thing: in my virtual machine, it works!! With the same code! How it's possible?
I think that is the aeroglass, but I change the theme to the Windows 7 Basic (same in the virtual machine) and still don't work.
Thanks, and sorry for my english.
Could you try using SelectedItem? you could always create a new property and bind to this and then set this item directly rather than using the selected index. Hopefully this would trigger any additional logic in the DataGrid control :)
//Declare property outside of method
public ObjectType SelectedItem { get; set; }
//Set datacontext on load
DataContext = this;
myDatagrid.ItemsSource = myListOfObjects.Where(item => item.Name.Contains(MyTextBox.Text)); //Filter
if (myDatagrid.Items.Count > 0) // If no itens, then do nothing
{
SelectedItem = myDatagrid.ItemSource[0]; // If has at least one item, select the first
}
myDatagrid.Items.Refresh();
Also don't forget to set your binding!
SelectedItem="{Binding SelectedItem}"
hope that helps!
I'am using the silverlight 4 dataform on a view with the MVVM pattern (and the "simple mvvm toolkit") and RIA services (and EntityFramework on server side).
My dataform is Bind to a PagedCollectionView on the viewModel. The logical context is "A 'Region' has * 'Territories' and a 'Territory' is linked to a 'Region'" (I recreate the original problem on Nothwind database to explain it).
<toolkit:DataForm Name="DataForm"
ItemsSource="{Binding RegionTerritories}"
CurrentItem="{Binding TerritorySelected, Mode=TwoWay}"
AutoGenerateFields="True"
AutoEdit="True"
AutoCommit="False"
IsReadOnly="False"
Margin="0,10,0,0" EditEnded="DataForm_EditEnded"
DeletingItem="DataForm_DeletingItem" AddingNewItem="DataForm_AddingNewItem"
CurrentItemChanged="DataForm_CurrentItemChanged" />
The code of my View Model to load entities on the pagedCollectionView :
this.RegionTerritories = new PagedCollectionView(this.CurrentRegion.Territories.ToList());
this.RegionTerritories.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(RegionTerritories_CollectionChanged);
I watch changes on the PagedCollectionView to update my DomainContext (throught "serviceAgent") by listening the "CollectionChanged" event :
private void RegionTerritories_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var item in e.NewItems)
{
Territory territory = item as Territory;
if (territory != null)
{
this.regionServiceAgent.InitializeNewTerritoryAndAddToDomainContext((Territory)item);
territory.Region = this.CurrentRegion;
this.CurrentRegion.Territories.Add(territory);
}
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (var item in e.OldItems)
{
Territory territory = item as Territory;
if (territory != null)
{
if (this.CurrentRegion.Territories.Contains<Territory>(territory))
{
this.regionServiceAgent.MarkForDeleteTerritory(territory);
this.CurrentRegion.Territories.Remove(territory);
}
}
}
break;
//case NotifyCollectionChangedAction.Replace:
// break;
//case NotifyCollectionChangedAction.Reset:
// break;
}
}
And here is my problem : if "this.CurrentRegion.Territories" is empty (no Territories present), and if I add an item and then directly cancel it, the cancel action make me pass on "case NotifyCollectionChangedAction.Remove:" as expected. Everything works until 'this.CurrentRegion.Territories.Remove(territory);' this line of code create a "'System.NullReferenceException' occurs on System.Windows.Controls.Data.DataForm.Toolkit". The Exception appends excatly on "this.RaiseDataMemberChanged("RegionID");" when "Territory" entity is doing internal update : .
/// <summary>
/// Obtient ou définit la valeur « RegionID ».
/// </summary>
[DataMember()]
[RoundtripOriginal()]
public int RegionID
{
get
{
return this._regionID;
}
set
{
if ((this._regionID != value))
{
this.OnRegionIDChanging(value);
this.RaiseDataMemberChanging("RegionID");
this.ValidateProperty("RegionID", value);
this._regionID = value;
this.RaiseDataMemberChanged("RegionID"); // THIS CAUSE DATAFORM FAIL !!
this.OnRegionIDChanged();
}
}
}
The strange things is that if "this.CurrentRegion.Territories" is not empty at the begining, eveything works fine...
What I'am doing wrong ?
The download link of the solution for code details:
https://skydrive.live.com/redir.aspx?cid=e41cfc4c2d6196d4&resid=E41CFC4C2D6196D4!252&parid=E41CFC4C2D6196D4!215&authkey=!AALulK7qBi40vJk
Lauch solution, go to "regions" page, and then click on the button of the last item on list (with territories equals 0). And then click on cancel on the dataform's childwindows. This should make bug appears.
Thanks for any help !
This answer help me to resolve the problem :
How to apply a Filter to a PagedCollectionView but prevent CollectionChanged event from firing on edit?
The solution is to not listen to the CollectionChanged event of the PagedCollectionView, but to listen to the CollectionChanged event of the PagedCollectionView.SourceCollection.
So the code become :
ObservableCollection<Territory> territories = new ObservableCollection<Territory>(this.CurrentRegion.Territories);
this.RegionTerritories = new PagedCollectionView(territories);
territories.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(RegionTerritories_CollectionChanged);
Thanks for help Bryant, and thanks Stackoverflow !
I am trying to build a USerControl that contains a GMapControl. When I place the GMapControl directly on the Form, then it works as expected. If I however place the GMapControl on a UserControl, and then add that UserControl to the Form, I get errors.
For example:
My UserControl, Map.cs:
public Map()
{
InitializeComponent();
gMapControl1.MapProvider = GMapProviders.OpenStreetMap;
gMapControl1.Position = new PointLatLng(54.6961334816182, 25.2985095977783);
gMapControl1.MinZoom = 1;
gMapControl1.MaxZoom = 24;
gMapControl1.Zoom = 9;
top = new GMapOverlay("1");
objects = new GMapOverlay("objects");
routes = new GMapOverlay("routes");
polygons = new GMapOverlay("polygons");
gMapControl1.Overlays.Add(routes);
gMapControl1.Overlays.Add(polygons);
gMapControl1.Overlays.Add(objects);
gMapControl1.Overlays.Add(top);
gMapControl1.OnMarkerClick += new MarkerClick(gMapControl1_OnMarkerClick);
gMapControl1.OnPolygonClick += new PolygonClick(gMapControl1_OnPolygonClick);
}
Then I add this UserControl to my Form by dragging it on there. Then I get an Exception:
Failed to create component 'Map'. The error message follows:
'System.MissingMethodException: Method not found: 'Void
GMap.NET.WindowsForms.GMapControl.set_MapProvider(GMap.NET,
MapProviders.GMapProvider)'. at OpenStreetMapTest.Map..ctor()'
If I have the same code that I have in the UserControl Map inside a Form, then no errors. Also, the set_MapProvider exists and works if I don't put the GMapControl inside a UserControl.
Any ideas?
Decompile the code and see what the Map construtor is doing. Maybe it's locating some method by reflection. Can't think why else you'd get a MissingMethodException dependong on where the control is sitting.
On DesignMode suggestion, that property just flat out doesn't work for nested user controls which is really frustrating. However, you can use the following work around (this property would be in a UserControlBase class from which you would inherit)
Simply check IsDesignerHosted instead of IsDesignMode.
/// <summary>
/// Indicates if the code is being run in the context of the designer
/// </summary>
/// <remarks>
/// <see cref="Component.DesignMode"/> always returns false for nested controls. This is one
/// of the suggested work arounds here: http://stackoverflow.com/questions/34664/designmode-with-controls
/// </remarks>
public bool IsDesignerHosted
{
get
{
Control ctrl = this;
while (ctrl != null)
{
if ((ctrl.Site != null) && ctrl.Site.DesignMode)
return true;
ctrl = ctrl.Parent;
}
return false;
}
}
you should wrap everything inside the if ( !DesignMode )
eg.
Map()
{
InitializeComponent();
if ( !DesignMode )
{
gMapControl1.MapProvider = GMapProviders.OpenStreetMap;
gMapControl1.Position = new PointLatLng(54.6961334816182, 25.2985095977783);
gMapControl1.MinZoom = 1;
gMapControl1.MaxZoom = 24;
gMapControl1.Zoom = 9;
top = new GMapOverlay("1");
objects = new GMapOverlay("objects");
routes = new GMapOverlay("routes");
polygons = new GMapOverlay("polygons");
gMapControl1.Overlays.Add(routes);
gMapControl1.Overlays.Add(polygons);
gMapControl1.Overlays.Add(objects);
gMapControl1.Overlays.Add(top);
gMapControl1.OnMarkerClick += new MarkerClick(gMapControl1_OnMarkerClick);
gMapControl1.OnPolygonClick += new PolygonClick(gMapControl1_OnPolygonClick);
}
}
I have a WPF DataGrid control with a SelectionUnit of "FullRow" and SelectionMode of "Extended" that I'm programmatically selecting an item in (the first item, usually). The selection works, but for some reason any form of programmatic selection seems to break the shift-select multiselect ability.
If I single click another item in the DataGrid (so the item I just clicked is the only item selected), then shift-select will work. It only seems to break if I've programmatically selected the item. Additionally, control-click works to select multiple items in either case -- it seems to only be shift-select that is broken.
I've tried various forms of programmatically selecting the single item, from as simple as myGrid.SelectedIndex = 0, to using the DataGrid's ItemContainerGenerator to get an instance of the DataGridRow object and setting IsSelected = true on it, but to no avail.
To re-iterate -- programmatic selection of an item works, but it breaks shift-click selection.
Has anyone run into this before? I've tried setting focus on the DataGridRow instance that is programmatically selected, but it doesn't seem to help?
I succeeded to work around this problem using reflection:
var method = typeof(DataGrid).GetMethod("HandleSelectionForCellInput", BindingFlags.Instance | BindingFlags.NonPublic);
method.Invoke(MyDataGrid, new object[] { cellToSelect, false, false, false });
I struggled with this problem for multiple days and tried a lot of things that I found on the internet. In the end, I found the solution that works for me by studying the source code of the DataGrid.
In the DataGrid I noticed a member variable called _selectionAnchor and guessed that this must be the starting point for when a user expands the selection in the grid. My solution is to set this member to the first cell of the row that is selected. If a row is selected in code, than this fix makes sure that when expanding the selection it starts at the selected row.
Please note that I used the code from this issue to enable multiselect. Then, in file MainWindow.xaml.cs, I added this code:
private void ExampleDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ExampleDataGrid.SelectedItems.Count > 0)
{
ExampleDataGrid.ScrollIntoView(ExampleDataGrid.SelectedItems[0]);
// Make sure that when the user starts to make an extended selection, it starts at this one
foreach (var cellInfo in ExampleDataGrid.SelectedCells)
{
if (cellInfo.Column.DisplayIndex == 0)
{
var cell = GetDataGridCell(cellInfo);
cell?.Focus();
var field = typeof(DataGrid).GetField("_selectionAnchor", BindingFlags.NonPublic | BindingFlags.Instance);
field?.SetValue(ExampleDataGrid, cellInfo);
break;
}
}
}
}
public DataGridCell GetDataGridCell(DataGridCellInfo cellInfo)
{
var cellContent = cellInfo.Column.GetCellContent(cellInfo.Item);
if (cellContent != null)
{
return (DataGridCell)cellContent.Parent;
}
return null;
}
In the xaml file:
<vm:CustomDataGrid x:Name="ExampleDataGrid" ItemsSource="{Binding ImportItems}"
SelectedItemsList="{Binding SelectedImportItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="False" SelectionMode="Extended" IsReadOnly="True" CanUserAddRows="False"
SelectionChanged="ExampleDataGrid_SelectionChanged">
Remember there is a difference between focus and keyboard focus. When you select the item in code, check to see what control has Keyboard focus / regular focus. I'm guessing that the data grid loses this focus until you click on it with the mouse and then it regains the focus needed to use the ctrl function.
I ran into this issue in a WPF user control we were hosting inside a C++ application.
I just resolved exactly the same problem with the help of #ezolotko's snippet.
Because the grid is dynamically generating rows I needed to subscribe to ItemContainerGenerator.StatusChanged event and find the first cell in a row representing this element.
To find the cell I used DataGridHelper class and wrapped it all in an attached behaviour:
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using Speedwell.WPF.Helpers;
namespace Speedwell.WPF.Behaviors
{
public static class DataGridSingleRowSelected
{
public static readonly DependencyProperty IsSelectionFixEnabledProperty = DependencyProperty.RegisterAttached
(
"IsSelectionFixEnabled",
typeof(bool?),
typeof(DataGridSingleRowSelected),
new PropertyMetadata(null, IsSelectionFixEnabledChanged)
);
public static bool GetIsSelectionFixEnabled(DataGrid element)
{
return (bool)element.GetValue(IsSelectionFixEnabledProperty);
}
public static void SetIsSelectionFixEnabled(DataGrid element, bool value)
{
element.SetValue(IsSelectionFixEnabledProperty, value);
}
private static void IsSelectionFixEnabledChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var dataGrid = sender as DataGrid;
if(dataGrid != null)
{
if(args.OldValue == null)
{
dataGrid.ItemContainerGenerator.StatusChanged += (s, e) => ContainerStatusChanged(dataGrid, ((ItemContainerGenerator)s));
}
}
}
private static void ContainerStatusChanged(DataGrid dataGrid, ItemContainerGenerator generator)
{
if(generator != null && generator.Status == GeneratorStatus.ContainersGenerated && dataGrid.SelectedItems.Count == 1)
{
var row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromItem(dataGrid.SelectedItems[0]);
if(row != null)
{
var cell = dataGrid.GetCell(row, 0);
if(cell != null)
{
SelectCellMethod.Invoke(dataGrid, new object[] { cell, false, false, false });
}
}
}
}
private static readonly MethodInfo SelectCellMethod = typeof(DataGrid).GetMethod("HandleSelectionForCellInput", BindingFlags.Instance | BindingFlags.NonPublic);
}
}
As you can see the proper selection is only applied when there is a single (1) row selected and this is exactly what I need and it seems it also what #Jordan0Day requested.
When a user clicks in certain places in my control, I want to change the color of some rows and columns in my grid, then fade it back to the normal color, within say 500ms or so. I haven't decided whether to use Winforms or WPF yet, so advice in either of those technologies would work. Thank you.
Edit: I understand I could do this by just calling Paint in a loop within the click event, properly setting the drawing parameters. However I believe that would block the UI, and I would like to be more responsive than that.
WPF has very good support for animations. Animations are supported from both xaml and code behind, so you should be able to achieve any look that you are going for.
The MSDN Animation Overview for WPF looks to have a lot of good information for getting you started.
Here is one way you could handle the fade:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsApplication1
{
public class FadeForm : Form
{
private Timer fadeTimer;
private Panel fadePanel;
private Button fadeButton;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose( bool disposing )
{
if ( disposing && ( components != null ) )
{
components.Dispose();
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.fadePanel = new System.Windows.Forms.Panel();
this.fadeButton = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// fadePanel
//
this.fadePanel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.fadePanel.Location = new System.Drawing.Point( 4, 8 );
this.fadePanel.Name = "fadePanel";
this.fadePanel.Size = new System.Drawing.Size( 276, 104 );
this.fadePanel.TabIndex = 0;
//
// fadeButton
//
this.fadeButton.Location = new System.Drawing.Point( 104, 116 );
this.fadeButton.Name = "fadeButton";
this.fadeButton.Size = new System.Drawing.Size( 75, 23 );
this.fadeButton.TabIndex = 1;
this.fadeButton.Text = "Fade";
this.fadeButton.UseVisualStyleBackColor = true;
this.fadeButton.Click += new System.EventHandler( this.HandleFadeButtonClick );
//
// FadeForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF( 6F, 13F );
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size( 284, 142 );
this.Controls.Add( this.fadeButton );
this.Controls.Add( this.fadePanel );
this.Name = "FadeForm";
this.Text = "Fade Form";
this.ResumeLayout( false );
}
#endregion
public FadeForm()
{
InitializeComponent();
this.fadeTimer = new Timer();
}
private void HandleFadeButtonClick( object sender, EventArgs e )
{
this.fadeTimer.Tick += new EventHandler( HandleFadeTimerTick );
this.fadePanel.BackColor = Color.Red;
this.fadeTimer.Interval = 100;
this.fadeTimer.Start();
}
void HandleFadeTimerTick( object sender, EventArgs e )
{
Color panelColor = this.fadePanel.BackColor;
if ( panelColor.A > 0 )
{
this.fadePanel.BackColor =
Color.FromArgb(
Math.Max( panelColor.A - 20, 0 ),
panelColor.R, panelColor.G, panelColor.B );
}
else
{
this.fadeTimer.Stop();
}
}
}
}
Unfortunately, this approach doesn't seem to work with rows in a DataGridView. I don't know the reason, but the color doesn't show at all if the alpha component of the color isn't 255. If you can find a way around that, this code might help.
At its simplest, a fade effect like this just requires a timer of some sort that gradates the color back towards normal with each tick. The faster the time, the more discrete colors you will display from start to finish, and the smoother the overall effect will be (WPF may have something built-in to do this).
You definitely do not want to repaint in a loop. As you pointed out this will block the UI, and also you would not be able to control how long the loop takes (different machines will render the same number of steps from highlight color to normal in different lengths of time).