I recently asked a question on how to reorder an ItemsControl using Drag and Drop (ItemsControl Drag and Drop). The answer worked great for the time being (code below) but now I am trying to implement MVVM and the current solution requires access to items in the view. Any ideas how to change this to work with MVVM? I plan to make attached properties to bind to commands but I don't know how to get rid of lines such as: int index = (int)(e.GetPosition(DimsContainer).X / width);
Current drag and drop code:
private void DimsContainer_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_isDown = true;
_startPoint = e.GetPosition(this.DimsContainer);
}
private void DimsContainer_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_isDown = false;
_isDragging = false;
if (_realDragSource != null)
{
_realDragSource.ReleaseMouseCapture();
}
}
private void DimsContainer_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (_isDown)
{
if ((_isDragging == false) &&
((Math.Abs(e.GetPosition(this.DimsContainer).X - _startPoint.X) >
SystemParameters.MinimumHorizontalDragDistance) ||
(Math.Abs(e.GetPosition(this.DimsContainer).Y - _startPoint.Y) >
SystemParameters.MinimumVerticalDragDistance)))
{
_isDragging = true;
_realDragSource = e.Source as UIElement;
_realDragSource.CaptureMouse();
double width = ((FrameworkElement)(this.DimsContainer.ItemContainerGenerator.ContainerFromIndex(0))).ActualWidth;
int index = (int)(e.GetPosition(DimsContainer).X / width);
DragDrop.DoDragDrop(_realDragSource, new DataObject("UIElement", index, true), DragDropEffects.Move);
}
}
}
private void DimsContainer_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent("UIElement"))
{
e.Effects = DragDropEffects.Move;
}
}
private void DimsContainer_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent("UIElement"))
{
int sourceIndex = (int)e.Data.GetData("UIElement");
int removedObject = this.indices[sourceIndex];
this.indices.RemoveAt(sourceIndex);
UIElement droptarget = e.Source as UIElement;
// If a drag/drop is happening, there is definitely
// one child in the DimsContainer
double width = ((FrameworkElement)(this.DimsContainer.ItemContainerGenerator.ContainerFromIndex(0))).ActualWidth;
int index = (int)(e.GetPosition(DimsContainer).X / width);
try
{
this.indices.Insert(index, removedObject);
}
catch (InvalidOperationException)
{
// ignore
}
_isDown = false;
_isDragging = false;
_realDragSource.ReleaseMouseCapture();
}
}
public static int RemoveItemFromItemsControl(ItemsControl itemsControl, object itemToRemove)
{
int indexToBeRemoved = -1;
if (itemToRemove != null)
{
indexToBeRemoved = itemsControl.Items.IndexOf(itemToRemove);
if (indexToBeRemoved != -1)
{
IEnumerable itemsSource = itemsControl.ItemsSource;
if (itemsSource == null)
{
itemsControl.Items.RemoveAt(indexToBeRemoved);
}
// Is the ItemsSource IList or IList<T>? If so, remove the item from the list.
else if (itemsSource is IList)
{
((IList)itemsSource).RemoveAt(indexToBeRemoved);
}
else
{
Type type = itemsSource.GetType();
Type genericIListType = type.GetInterface("IList`1");
if (genericIListType != null)
{
type.GetMethod("RemoveAt").Invoke(itemsSource, new object[] { indexToBeRemoved });
}
}
}
}
return indexToBeRemoved;
}
public static void InsertItemInItemsControl(ItemsControl itemsControl, object itemToInsert, int insertionIndex)
{
if (itemToInsert != null)
{
IEnumerable itemsSource = itemsControl.ItemsSource;
if (itemsSource == null)
{
itemsControl.Items.Insert(insertionIndex, itemToInsert);
}
// Is the ItemsSource IList or IList<T>? If so, insert the dragged item in the list.
else if (itemsSource is IList)
{
((IList)itemsSource).Insert(insertionIndex, itemToInsert);
}
else
{
Type type = itemsSource.GetType();
Type genericIListType = type.GetInterface("IList`1");
if (genericIListType != null)
{
type.GetMethod("Insert").Invoke(itemsSource, new object[] { insertionIndex, itemToInsert });
}
}
}
}
use drag and drop behavior. e.g. http://www.codeproject.com/KB/WPF/gong-wpf-dragdrop-ii.aspx
Related
I'm writing an app where i can drag from one list to another one, but i can also drag from the same list to itself. Is there anyway to know which list is the source list (I mean which list i dragged from) or what data i dragged?
I have 2 lists: lbTwo and lbOne, and two datas (one for each list of ObservableCollection): firstDataType and secondDataType.
The only way i found to get the data is (But i prefer to get the list name and not the data it contains):
private void ListBox_Drop(object sender, DragEventArgs e)
{
ListBox parent = (ListBox)sender;
string data_type = (e.Data.GetFormats())[0];
}
but it's not really elegant and i also get the whole name (Myapp.firstDataType).
Thanks!!
Here is how my code looks like:
Xaml:
<ListBox Name="LeftBox" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
PreviewMouseLeftButtonDown="Common_PreviewMouseLeftButtonDown"
AllowDrop="True"
Drop="Common_Drop"/>
<ListBox Name="RightBox" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
PreviewMouseLeftButtonDown="Common_PreviewMouseLeftButtonDown"
AllowDrop="True"
Drop="Common_Drop"/>
I call this method to load test data into my Listboxes:
private void LoadData()
{
var lstbox1Data = new string[] { "One", "Two", "Three", "Four", "Five" };
var lstbox2Data = new string[] { "Alpha", "Beta", "Theta", "Gamma", "Phi" };
foreach (var item in lstbox1Data)
{
LeftBox.Items.Add(item);
}
foreach (var item in lstbox2Data)
{
RightBox.Items.Add(item);
}
}
This is the actual implementation of DragDrop across Listboxes & re-arrange within Listbox.
private void Common_Drop(object sender, DragEventArgs e)
{
var destLisBox = sender as ListBox;
var dragDropData = e.Data.GetData(typeof(DragDropData)) as DragDropData;
if (dragDropData != null)
{
var dataWhereToInsert = GetDataFromListBox(destLisBox, e.GetPosition(destLisBox));
if (dataWhereToInsert != null)
{
var insertAtIndex = destLisBox.Items.IndexOf(dataWhereToInsert);
if (insertAtIndex >= 0)
{
dragDropData.DragStartSource.Items.Remove(dragDropData.ActualData);
destLisBox.Items.Insert(insertAtIndex, dragDropData.ActualData);
}
}
else
{
dragDropData.DragStartSource.Items.Remove(dragDropData.ActualData);
destLisBox.Items.Add(dragDropData.ActualData);
}
}
}
private static object GetDataFromListBox(ListBox source, Point point)
{
UIElement element = source.InputHitTest(point) as UIElement;
if (element != null)
{
object data = DependencyProperty.UnsetValue;
while (data == DependencyProperty.UnsetValue)
{
data = source.ItemContainerGenerator.ItemFromContainer(element);
if (data == DependencyProperty.UnsetValue)
{
element = VisualTreeHelper.GetParent(element) as UIElement;
}
if (element == source)
{
return null;
}
}
if (data != DependencyProperty.UnsetValue)
{
return data;
}
}
return null;
}
private void Common_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var srcListBox = sender as ListBox;
if (srcListBox != null)
{
var data = GetDataFromListBox(srcListBox, e.GetPosition(srcListBox));
if (data != null)
{
var dragDropData = new DragDropData
{
ActualData = data,
DragStartSource = srcListBox
};
DragDrop.DoDragDrop(srcListBox, dragDropData, DragDropEffects.Move);
}
}
}
Note that I wrapped the data & sender into below class:
public class DragDropData
{
public object ActualData { get; set; }
public ListBox DragStartSource { get; set; }
}
That amazing : GetDataFromListBox method comes from:
[http://www.c-sharpcorner.com/uploadfile/dpatra/drag-and-drop-item-in-listbox-in-wpf/]
I'm stuck with some legacy code that I want to upgrade a bit. I want to change the way the ErrorProvider shows the error status on a Control. Default behavior is the Icon, and a ToolTip if you hover on the icon.
I would like to change this behavior to be more similar to what we use in our WPF controls. Which is a red back-color(Salmon pink) and the tool-tip on the control itself.
Any tips, links or some way forward
EDIT.
See my answer below, on what i ended up with.
ErrorProvider component doesn't support this feature and if you need it you can create it yourself.
You can subscribe to BindingComplete event of a BindingManagerBase and then you can use the event arg which is of type BindingCompleteEventArgs that contains some useful properties:
ErrorText to determine if there is an error in data-binding
Binding.Control to determine the control which is bounded to
These are enough for us to implement our solution.
Code
Here is a sample code which shows how can you handle BindingComplete event and use it to change BackColor and tool-tip of a control based on it's valid or invalid state.
Suppose you have a binding source, myBindingSource which is bound to a SampleModel class which is implemented IDataErrorInfo. You can subscribe to BindingComplete event of this.BindingContext[this.myBindingSource]:
private void Form1_Load(object sender, EventArgs e)
{
this.myBindingSource.DataSource = new SampleModel();
var bindingManager = this.BindingContext[this.myBindingSource];
bindingManager.BindingComplete += bindingManager_BindingComplete;
}
Dictionary<Control, Color> Items = new Dictionary<Control, Color>();
private void bindingManager_BindingComplete(object sender, BindingCompleteEventArgs e)
{
var control = e.Binding.Control;
//Store Original BackColor
if (!Items.ContainsKey(control))
Items[control] = control.BackColor;
//Check If there is an error
if (!string.IsNullOrEmpty(e.ErrorText))
{
control.BackColor = Color.Salmon;
this.errorToolTip.SetToolTip(control, e.ErrorText);
}
else
{
e.Binding.Control.BackColor = Items[e.Binding.Control];
this.errorToolTip.SetToolTip(control, null);
}
}
Thank you Reza Aghaei. This is what i came up with based on your comment and some additional searching... Some of this code comes from msdn resource
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Linq;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ErrorProvider
{
class BackgroundColorErrorProvider: Component, IExtenderProvider, ISupportInitialize
{
public BackgroundColorErrorProvider()
{
currentChanged = new EventHandler(ErrorManager_CurrentChanged);
}
public BackgroundColorErrorProvider(ContainerControl parentControl)
: this()
{
this.parentControl = parentControl;
propChangedEvent = new EventHandler(ParentControl_BindingContextChanged);
parentControl.BindingContextChanged += propChangedEvent;
}
public BackgroundColorErrorProvider(IContainer container)
: this()
{
if (container == null) {
throw new ArgumentNullException("container");
}
container.Add(this);
}
public bool CanExtend(object extendee)
{
return extendee is Control && !(extendee is Form) && !(extendee is ToolBar);
}
private bool inSetErrorManager = false;
private object dataSource;
private string dataMember = null;
private ContainerControl parentControl;
private BindingManagerBase errorManager;
private bool initializing;
private EventHandler currentChanged;
private EventHandler propChangedEvent;
private Dictionary<Control, Color> originalColor = new Dictionary<Control, Color>();
private Color errorBackgroundColor;
public ContainerControl ContainerControl
{
[UIPermission(SecurityAction.LinkDemand, Window = UIPermissionWindow.AllWindows)]
[UIPermission(SecurityAction.InheritanceDemand, Window = UIPermissionWindow.AllWindows)]
get
{
return parentControl;
}
set
{
if (parentControl != value)
{
if (parentControl != null)
parentControl.BindingContextChanged -= propChangedEvent;
parentControl = value;
if (parentControl != null)
parentControl.BindingContextChanged += propChangedEvent;
Set_ErrorManager(this.DataSource, this.DataMember, true);
}
}
}
public string DataMember
{
get { return dataMember; }
set
{
if (value == null) value = "";
Set_ErrorManager(this.DataSource, value, false);
}
}
public object DataSource
{
get { return dataSource; }
set
{
if ( parentControl != null && value != null && String.IsNullOrEmpty(this.dataMember))
{
// Let's check if the datamember exists in the new data source
try
{
errorManager = parentControl.BindingContext[value, this.dataMember];
}
catch (ArgumentException)
{
// The data member doesn't exist in the data source, so set it to null
this.dataMember = "";
}
}
Set_ErrorManager(value, this.DataMember, false);
}
}
public override ISite Site
{
set
{
base.Site = value;
if (value == null)
return;
IDesignerHost host = value.GetService(typeof(IDesignerHost)) as IDesignerHost;
if (host != null)
{
IComponent baseComp = host.RootComponent;
if (baseComp is ContainerControl)
{
this.ContainerControl = (ContainerControl)baseComp;
}
}
}
}
private ToolTip toolTip;
public ToolTip ToolTip
{
get { return toolTip; }
set { toolTip = value; }
}
public Color ErrorBackgroundColor
{
get { return errorBackgroundColor; }
set { errorBackgroundColor = value; }
}
private void Set_ErrorManager(object newDataSource, string newDataMember, bool force)
{
if (inSetErrorManager)
return;
inSetErrorManager = true;
try
{
bool dataSourceChanged = this.DataSource != newDataSource;
bool dataMemberChanged = this.DataMember != newDataMember;
//if nothing changed, then do not do any work
//
if (!dataSourceChanged && !dataMemberChanged && !force)
{
return;
}
// set the dataSource and the dataMember
//
this.dataSource = newDataSource;
this.dataMember = newDataMember;
if (!initializing)
{
UnwireEvents(errorManager);
// get the new errorManager
//
if (parentControl != null && this.dataSource != null && parentControl.BindingContext != null)
{
errorManager = parentControl.BindingContext[this.dataSource, this.dataMember];
}
else
{
errorManager = null;
}
// wire the events
//
WireEvents(errorManager);
// see if there are errors at the current
// item in the list, w/o waiting for the position to change
if (errorManager != null)
UpdateBinding();
}
}
finally
{
inSetErrorManager = false;
}
}
public void UpdateBinding()
{
ErrorManager_CurrentChanged(errorManager, EventArgs.Empty);
}
private void UnwireEvents(BindingManagerBase listManager)
{
if (listManager != null)
{
listManager.CurrentChanged -= currentChanged;
listManager.BindingComplete -= new BindingCompleteEventHandler(this.ErrorManager_BindingComplete);
CurrencyManager currManager = listManager as CurrencyManager;
if (currManager != null)
{
currManager.ItemChanged -= new ItemChangedEventHandler(this.ErrorManager_ItemChanged);
currManager.Bindings.CollectionChanged -= new CollectionChangeEventHandler(this.ErrorManager_BindingsChanged);
}
}
}
private void WireEvents(BindingManagerBase listManager)
{
if (listManager != null)
{
listManager.CurrentChanged += currentChanged;
listManager.BindingComplete += new BindingCompleteEventHandler(this.ErrorManager_BindingComplete);
CurrencyManager currManager = listManager as CurrencyManager;
if (currManager != null)
{
currManager.ItemChanged += new ItemChangedEventHandler(this.ErrorManager_ItemChanged);
currManager.Bindings.CollectionChanged += new CollectionChangeEventHandler(this.ErrorManager_BindingsChanged);
}
}
}
private void ErrorManager_BindingsChanged(object sender, CollectionChangeEventArgs e)
{
ErrorManager_CurrentChanged(errorManager, e);
}
private void ParentControl_BindingContextChanged(object sender, EventArgs e)
{
Set_ErrorManager(this.DataSource, this.DataMember, true);
}
private void ErrorManager_ItemChanged(object sender, ItemChangedEventArgs e)
{
BindingsCollection errBindings = errorManager.Bindings;
int bindingsCount = errBindings.Count;
// If the list became empty then reset the errors
if (e.Index == -1 && errorManager.Count == 0)
{
for (int j = 0; j < bindingsCount; j++)
{
if ((errBindings[j].Control != null))
{
// ...ignore everything but bindings to Controls
SetError(errBindings[j].Control, "");
}
}
}
else
{
ErrorManager_CurrentChanged(sender, e);
}
}
private void SetError(Control control, string p)
{
if (String.IsNullOrEmpty(p))
{
if (originalColor.ContainsKey(control))
control.BackColor = originalColor[control];
toolTip.SetToolTip(control, null);
}
else
{
control.BackColor = ErrorBackgroundColor;
toolTip.SetToolTip(control, p);
}
}
private void ErrorManager_BindingComplete(object sender, BindingCompleteEventArgs e)
{
Binding binding = e.Binding;
if (binding != null && binding.Control != null)
{
SetError(binding.Control, (e.ErrorText == null ? String.Empty : e.ErrorText));
}
}
private void ErrorManager_CurrentChanged(object sender, EventArgs e)
{
if (errorManager.Count == 0)
{
return;
}
object value = errorManager.Current;
if (!(value is IDataErrorInfo))
{
return;
}
BindingsCollection errBindings = errorManager.Bindings;
int bindingsCount = errBindings.Count;
// We can only show one error per control, so we will build up a string...
//
Hashtable controlError = new Hashtable(bindingsCount);
for (int j = 0; j < bindingsCount; j++)
{
// Ignore everything but bindings to Controls
if (errBindings[j].Control == null)
{
continue;
}
string error = ((IDataErrorInfo)value)[errBindings[j].BindingMemberInfo.BindingField];
if (error == null)
{
error = "";
}
string outputError = "";
if (controlError.Contains(errBindings[j].Control))
outputError = (string)controlError[errBindings[j].Control];
// VSWhidbey 106890: Utilize the error string without including the field name.
if (String.IsNullOrEmpty(outputError))
{
outputError = error;
}
else
{
outputError = string.Concat(outputError, "\r\n", error);
}
controlError[errBindings[j].Control] = outputError;
}
IEnumerator enumerator = controlError.GetEnumerator();
while (enumerator.MoveNext())
{
DictionaryEntry entry = (DictionaryEntry)enumerator.Current;
SetError((Control)entry.Key, (string)entry.Value);
}
}
public void BeginInit()
{
initializing = true;
}
public void EndInit()
{
initializing = false;
Set_ErrorManager(this.DataSource, this.DataMember, true);
}
}
}
This is my code
gridView1.Columns.Add(new DevExpress.XtraGrid.Columns.GridColumn()
{
Caption = "Selected",
ColumnEdit = new RepositoryItemCheckEdit() { },
VisibleIndex = 1,
UnboundType = DevExpress.Data.UnboundColumnType.Boolean
});
But I cant check multiple checkEdit at the same time.
Why was that?
And please show me the way out.
Thanks.
Well, there are two answers to that question, one very simple, and one very complex, let's start with the simple:
If you want to have an column that has the "Selected" caption and act as a checkbox to indicate that a particular record was selected, you have two options:
1) If you can alter the class in your data source to add a property that is bool and could be used with DataBinding, then, all is done in a very simple way, jast add the property and bind the data and it will work:
class SimplePerson
{
public string Name { get; set; }
public bool IsSelected { get; set; }
}
BindingList<SimplePerson> source = new BindingList<SimplePerson>();
void InitGrid()
{
source.Add(new SimplePerson() { Name = "John", IsSelected = false });
source.Add(new SimplePerson() { Name = "Gabriel", IsSelected = true });
gridControl.DataSource = source;
}
2) You cannot alter you classes, so you need to this by signing the correct grid events and drawing the column yourself, and also adding the right handlers for all the actions.... is a very complex case, but for your luck i have this done, because i have had this problem in the past, so i will post you my full class!
public class GridCheckMarksSelection
{
public event EventHandler SelectionChanged;
protected GridView _view;
protected ArrayList _selection;
private GridColumn _column;
private RepositoryItemCheckEdit _edit;
public GridView View
{
get { return _view; }
set
{
if (_view == value)
return;
if (_view != null)
Detach();
_view = value;
Attach();
}
}
public GridColumn CheckMarkColumn { get { return _column; } }
public int SelectedCount { get { return _selection.Count; } }
public GridCheckMarksSelection()
{
_selection = new ArrayList();
}
public GridCheckMarksSelection(GridView view)
: this()
{
this.View = view;
}
protected virtual void Attach()
{
if (View == null)
return;
_selection.Clear();
_view = View;
_edit = View.GridControl.RepositoryItems.Add("CheckEdit")
as RepositoryItemCheckEdit;
_edit.EditValueChanged += edit_EditValueChanged;
_column = View.Columns.Insert(0);
_column.OptionsColumn.AllowSort = DefaultBoolean.False;
_column.VisibleIndex = int.MinValue;
_column.FieldName = "CheckMarkSelection";
_column.Caption = "Mark";
_column.OptionsColumn.ShowCaption = false;
_column.UnboundType = UnboundColumnType.Boolean;
_column.ColumnEdit = _edit;
View.CustomDrawColumnHeader += View_CustomDrawColumnHeader;
View.CustomDrawGroupRow += View_CustomDrawGroupRow;
View.CustomUnboundColumnData += view_CustomUnboundColumnData;
View.MouseUp += view_MouseUp;
}
protected virtual void Detach()
{
if (_view == null)
return;
if (_column != null)
_column.Dispose();
if (_edit != null)
{
_view.GridControl.RepositoryItems.Remove(_edit);
_edit.Dispose();
}
_view.CustomDrawColumnHeader -= View_CustomDrawColumnHeader;
_view.CustomDrawGroupRow -= View_CustomDrawGroupRow;
_view.CustomUnboundColumnData -= view_CustomUnboundColumnData;
_view.MouseDown -= view_MouseUp;
_view = null;
}
protected virtual void OnSelectionChanged(EventArgs e)
{
if (SelectionChanged != null)
SelectionChanged(this, e);
}
protected void DrawCheckBox(Graphics g, Rectangle r, bool Checked)
{
var info = _edit.CreateViewInfo() as CheckEditViewInfo;
var painter = _edit.CreatePainter() as CheckEditPainter;
ControlGraphicsInfoArgs args;
info.EditValue = Checked;
info.Bounds = r;
info.CalcViewInfo(g);
args = new ControlGraphicsInfoArgs(info, new GraphicsCache(g), r);
painter.Draw(args);
args.Cache.Dispose();
}
private void view_MouseUp(object sender, MouseEventArgs e)
{
if (e.Clicks == 1 && e.Button == MouseButtons.Left)
{
GridHitInfo info;
var pt = _view.GridControl.PointToClient(Control.MousePosition);
info = _view.CalcHitInfo(pt);
if (info.InRow && _view.IsDataRow(info.RowHandle))
UpdateSelection();
if (info.InColumn && info.Column == _column)
{
if (SelectedCount == _view.DataRowCount)
ClearSelection();
else
SelectAll();
}
if (info.InRow && _view.IsGroupRow(info.RowHandle)
&& info.HitTest != GridHitTest.RowGroupButton)
{
bool selected = IsGroupRowSelected(info.RowHandle);
SelectGroup(info.RowHandle, !selected);
}
}
}
private void View_CustomDrawColumnHeader
(object sender, ColumnHeaderCustomDrawEventArgs e)
{
if (e.Column != _column)
return;
e.Info.InnerElements.Clear();
e.Painter.DrawObject(e.Info);
DrawCheckBox(e.Graphics, e.Bounds, SelectedCount == _view.DataRowCount);
e.Handled = true;
}
private void View_CustomDrawGroupRow
(object sender, RowObjectCustomDrawEventArgs e)
{
var info = e.Info as GridGroupRowInfo;
info.GroupText = " " + info.GroupText.TrimStart();
e.Info.Paint.FillRectangle
(e.Graphics, e.Appearance.GetBackBrush(e.Cache), e.Bounds);
e.Painter.DrawObject(e.Info);
var r = info.ButtonBounds;
r.Offset(r.Width * 2, 0);
DrawCheckBox(e.Graphics, r, IsGroupRowSelected(e.RowHandle));
e.Handled = true;
}
private void view_CustomUnboundColumnData
(object sender, CustomColumnDataEventArgs e)
{
if (e.Column != CheckMarkColumn)
return;
if (e.IsGetData)
e.Value = IsRowSelected(View.GetRowHandle(e.ListSourceRowIndex));
else
SelectRow(View.GetRowHandle(e.ListSourceRowIndex), (bool)e.Value);
}
private void edit_EditValueChanged(object sender, EventArgs e)
{
_view.PostEditor();
}
private void SelectRow(int rowHandle, bool select, bool invalidate)
{
if (IsRowSelected(rowHandle) == select)
return;
object row = _view.GetRow(rowHandle);
if (select)
_selection.Add(row);
else
_selection.Remove(row);
if (invalidate)
Invalidate();
OnSelectionChanged(EventArgs.Empty);
}
public object GetSelectedRow(int index)
{
return _selection[index];
}
public int GetSelectedIndex(object row)
{
return _selection.IndexOf(row);
}
public void ClearSelection()
{
_selection.Clear();
View.ClearSelection();
Invalidate();
OnSelectionChanged(EventArgs.Empty);
}
private void Invalidate()
{
_view.CloseEditor();
_view.BeginUpdate();
_view.EndUpdate();
}
public void SelectAll()
{
_selection.Clear();
var dataSource = _view.DataSource as ICollection;
if (dataSource != null && dataSource.Count == _view.DataRowCount)
_selection.AddRange(dataSource); // fast
else
for (int i = 0; i < _view.DataRowCount; i++) // slow
_selection.Add(_view.GetRow(i));
Invalidate();
OnSelectionChanged(EventArgs.Empty);
}
public void SelectGroup(int rowHandle, bool select)
{
if (IsGroupRowSelected(rowHandle) && select) return;
for (int i = 0; i < _view.GetChildRowCount(rowHandle); i++)
{
int childRowHandle = _view.GetChildRowHandle(rowHandle, i);
if (_view.IsGroupRow(childRowHandle))
SelectGroup(childRowHandle, select);
else
SelectRow(childRowHandle, select, false);
}
Invalidate();
}
public void SelectRow(int rowHandle, bool select)
{
SelectRow(rowHandle, select, true);
}
public bool IsGroupRowSelected(int rowHandle)
{
for (int i = 0; i < _view.GetChildRowCount(rowHandle); i++)
{
int row = _view.GetChildRowHandle(rowHandle, i);
if (_view.IsGroupRow(row))
if (!IsGroupRowSelected(row))
return false;
else
if (!IsRowSelected(row))
return false;
}
return true;
}
public bool IsRowSelected(int rowHandle)
{
if (_view.IsGroupRow(rowHandle))
return IsGroupRowSelected(rowHandle);
object row = _view.GetRow(rowHandle);
return GetSelectedIndex(row) != -1;
}
public void UpdateSelection()
{
_selection.Clear();
Array.ForEach(View.GetSelectedRows(), item => SelectRow(item, true));
}
}
And now you need to know how to use this:
void InitGrid()
{
gridControl.DataSource = source;
// Do this after the database for the grid is set!
selectionHelper = new GridCheckMarksSelection(gridView1);
// Define where you want the column (0 = first)
selectionHelper.CheckMarkColumn.VisibleIndex = 0;
// You can even subscrive to the event that indicates that
// there was change in the selection.
selectionHelper.SelectionChanged += selectionHelper_SelectionChanged;
}
void selectionHelper_SelectionChanged(object sender, EventArgs e)
{
// Do something when the user selects or unselects something
}
But how do you retrieve all the selected items? There is a example assuming that the type bond is 'Person'
/// <summary>
/// Return all selected persons from the Grid
/// </summary>
public IList<Person> GetItems()
{
var ret = new List<Person>();
Array.ForEach
(
gridView1.GetSelectedRows(),
cell => ret.Add(gridView1.GetRow(cell) as Person)
);
return ret;
}
I am working on a WPF web application with a MainPage which is using ninject to load pages.
Basically I am woundering if there is a way to tell NavigationService that an Id has changed because a user updated a value in a composite key.
I'll do my best to explain a little more. My MultiTagPage has a MultiTagViewModel and it shows a multitagvalue consisting of (MultiTagDef, Key, Value). There is also a DataGrid with other values with the same key and MultiTagDef. If you dubble click on one of those values the corresponding multitagvalue is showed instead.
In the database the multitagvalues are stored with composite keys (Multitagdef.Name, key, value). So if somebody updates a multitagvalue the Id is changed (say from (A, B, C) to (A, B, D)) and saved, then if the user proceeds to another multitagvalue in the datagrid (A, B, E) and perhaps deletes that object then the navigationservice try to load (A, B, C) instead of (A, B, D).
The architecture is designed for objects which has an ID column which, of course, never changes. Unfortunately adding an ID column is not an option in this case. So does anyone have a suggestion of how to solve this? Should I try to reload the page everytime somebody saves or could I tell the NavigationService that the current object now has changed ID?
Now, code:
From MainPage:
public partial class MainPage : IUIService
{
public static readonly DependencyProperty MainModelProperty = PropertyHelper.Register<MainPage>(x => x.MainModel);
public MainModel MainModel
{
get { return (MainModel)GetValue(MainModelProperty); }
set { SetValue(MainModelProperty, value); }
}
private static readonly ILog log = LogManager.GetLogger(typeof(MainPage));
public MainPage()
{
// Make doubly sure...
ShowsNavigationUI = false;
InitializeComponent();
App.Kernel.Rebind<IUIService>().ToConstant(this);
MainModel = App.Kernel.Get<MainModel>();
WindowTitle = MainModel.Title;
ContentFrame.Navigating += Navigating;
ContentFrame.Navigated += Navigated;
ContentFrame.NavigationFailed += NavigationFailed;
}
private void Navigating(object sender, NavigatingCancelEventArgs args)
{
object dataContext = null;
if(ContentFrame.Content is FrameworkElement) {
dataContext = ((FrameworkElement)ContentFrame.Content).DataContext;
} else if(ContentFrame.Content is FrameworkContentElement) {
dataContext = ((FrameworkContentElement)ContentFrame.Content).DataContext;
}
if(dataContext is ISaveable && ((ISaveable)dataContext).NeedsSave) {
if(MessageControl.UnsavedSync() != MessageControl.Button.Yes) {
args.Cancel = true;
}
}
}
private void Navigated(object sender, NavigationEventArgs e)
{
var mi = e.ExtraData as MenuItemModel;
if(mi == null) {
var page = e.Content as IMenuItemPage;
if(page != null) {
mi = page.MenuItem;
}
if(mi == null) {
log.DebugFormat("Navigated to {0} ({1}) without menu item", e.Content, e.Uri);
return;
}
}
MainModel.CurrentMenuItem = mi;
if(mi.Type != MenuItemType.Folder) {
Settings.Default.AddRecentMenuItem(new RecentMenuItem(mi.MenuItem));
}
}
#region Generic Edit command
private void EditCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.Handled = true;
e.CanExecute = false;
var param = Unwrap(e.Parameter);
var paramType = GetEditType(param);
if(paramType == null || !editPages.ContainsKey(paramType)) {
return;
}
e.CanExecute = ToConstructorArgument(param) != null;
}
private void EditExecuted(object sender, ExecutedRoutedEventArgs e)
{
Edit(e.Parameter);
}
private void Edit(object param)
{
var paramType = GetEditType(param);
if(paramType == null || !editPages.ContainsKey(paramType)) {
log.WarnFormat("Page for param {0} (type {1}) not found", param, paramType);
return;
}
if(param is IList) {
var list = (IList)param;
if(list.Count > 1000) {
Show("Too many items selected", "Please select no more than 1000 items at a time", messageTheme: MessageTheme.Warning);
return;
}
}
var arg = ToConstructorArgument(param);
if(arg == null) {
log.Warn("Unexpected parameter " + param + " supplied to EditExecuted");
return;
}
var pageType = editPages[paramType];
try {
log.DebugFormat("Got a parameter of type {0}, navigating to edit page {1}", param.GetType(), pageType);
Navigate(MakePage(pageType, arg));
} catch(Exception ex) {
log.Error("Unable to load edit page for parameter " + param, ex);
}
}
private static Page MakePage(Type pageType, params IParameter[] p)
{
var page = (Page)App.Kernel.Get(pageType, p);
var dp = ClientUtil.GetViewModelProperty(page.GetType());
if(dp != null) {
page.Loaded += (o, args) => {
var skrap = App.Kernel.Get(dp.PropertyType, p);
page.SetValue(dp, App.Kernel.Get(dp.PropertyType, p));
};
page.Unloaded += (o, args) => {
try {
page.ClearValue(dp);
} catch(Exception e) {
// Often happens when datagrid is in edit mode when navigating away from page:
// http://connect.microsoft.com/VisualStudio/feedback/details/571967/wpf-datagrid-causes-crash-with-sorting-is-not-allowed-during-an-addnew-or-edititem-transaction
log.Warn("Error while unloading page", e);
}
};
ViewModelBehavior.SetUpdateUIError(page, true);
}
return page;
}
#endregion
#region Navigate command
private void NavigateCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = e.Parameter is Type;
e.Handled = true;
}
private void NavigateExecuted(object sender, ExecutedRoutedEventArgs e)
{
var type = e.Parameter as Type;
if(type == null) {
return;
}
Navigate(MakePage(type));
e.Handled = true;
}
#endregion
#region IUIService implementation
public void Navigate(Type type, params Tuple<string, object>[] parameters)
{
Navigate(MakePage(type, parameters.Select(x => new ConstructorArgument(x.Item1, x.Item2)).ToArray()));
}
public void Navigate(Type type, MenuItemModel menuItem, params Tuple<string, object>[] parameters)
{
Navigate(MakePage(type, parameters.Select(x => new ConstructorArgument(x.Item1, x.Item2))
.Prepend(new ConstructorArgument("menuItem", menuItem))
.Prepend(new ConstructorArgument("props", menuItem.Params))
.ToArray()),
menuItem);
}
public void Navigate(Page content, object extraData = null)
{
if(ContentFrame != null) {
if(ContentFrame.Content is DependencyObject) {
foreach(var dg in WpfUtil.FindDescendants<DataGrid>((DependencyObject)ContentFrame.Content)) {
while(!dg.CommitEdit()) { /* Keep committing */ }
}
}
ContentFrame.Navigate(content, extraData);
}
}
public bool NavigateBack()
{
if(ContentFrame != null && ContentFrame.CanGoBack) {
ContentFrame.GoBack();
return true;
}
if(NavigationService != null && NavigationService.CanGoBack) {
NavigationService.GoBack();
return true;
}
return false;
}
}
Please tell me if you need more information.
I'm using PropertyGrid to edit an object containing a collection.
Collection is edited using the CollectionEditor.
I have to make sure elements in collection are unique.
How can I add validation to CollectionEditor:
By either overloading CollectionEditor's OnFormClosing
Or adding validation for creating/editing items?
You can create your own collection editor, and hook into events on the default editor's controls. You can use these events to, say, disable the OK button. Something like:
public class MyCollectionEditor : CollectionEditor
{
private static Dictionary<CollectionForm, Button> okayButtons
= new Dictionary<CollectionForm, Button>();
// Inherit the default constructor from CollectionEditor
public MyCollectionEditor(Type type)
: base(type)
{
}
// Override this method in order to access the containing user controls
// from the default Collection Editor form or to add new ones...
protected override CollectionForm CreateCollectionForm()
{
CollectionForm collectionForm = base.CreateCollectionForm();
collectionForm.FormClosed +=
new FormClosedEventHandler(collectionForm_FormClosed);
collectionForm.Load += new EventHandler(collectionForm_Load);
if (collectionForm.Controls.Count > 0)
{
TableLayoutPanel mainPanel = collectionForm.Controls[0]
as TableLayoutPanel;
if ((mainPanel != null) && (mainPanel.Controls.Count > 7))
{
// Get a reference to the inner PropertyGrid and hook
// an event handler to it.
PropertyGrid propertyGrid = mainPanel.Controls[5]
as PropertyGrid;
if (propertyGrid != null)
{
propertyGrid.PropertyValueChanged +=
new PropertyValueChangedEventHandler(
propertyGrid_PropertyValueChanged);
}
// Also hook to the Add/Remove
TableLayoutPanel buttonPanel = mainPanel.Controls[1]
as TableLayoutPanel;
if ((buttonPanel != null) && (buttonPanel.Controls.Count > 1))
{
Button addButton = buttonPanel.Controls[0] as Button;
if (addButton != null)
{
addButton.Click += new EventHandler(addButton_Click);
}
Button removeButton = buttonPanel.Controls[1] as Button;
if (removeButton != null)
{
removeButton.Click +=
new EventHandler(removeButton_Click);
}
}
// Find the OK button, and hold onto it.
buttonPanel = mainPanel.Controls[6] as TableLayoutPanel;
if ((buttonPanel != null) && (buttonPanel.Controls.Count > 1))
{
Button okayButton = buttonPanel.Controls[0] as Button;
if (okayButton != null)
{
okayButtons[collectionForm] = okayButton;
}
}
}
}
return collectionForm;
}
private static void collectionForm_FormClosed(object sender,
FormClosedEventArgs e)
{
CollectionForm collectionForm = (CollectionForm)sender;
if (okayButtons.ContainsKey(collectionForm))
{
okayButtons.Remove(collectionForm);
}
}
private static void collectionForm_Load(object sender, EventArgs e)
{
ValidateEditValue((CollectionForm)sender);
}
private static void propertyGrid_PropertyValueChanged(object sender,
PropertyValueChangedEventArgs e)
{
ValidateEditValue((CollectionForm)sender);
}
private static void addButton_Click(object sender, EventArgs e)
{
Button addButton = (Button)sender;
ValidateEditValue((CollectionForm)addButton.Parent.Parent.Parent);
}
private static void removeButton_Click(object sender, EventArgs e)
{
Button removeButton = (Button)sender;
ValidateEditValue((CollectionForm)removeButton.Parent.Parent.Parent);
}
private static void ValidateEditValue(CollectionForm collectionForm)
{
if (okayButtons.ContainsKey(collectionForm))
{
Button okayButton = okayButtons[collectionForm];
IList<MyClass> items = collectionForm.EditValue as IList<MyClass>;
okayButton.Enabled = MyCollectionIsValid(items);
}
}
private static bool MyCollectionIsValid(IList<MyClass> items)
{
// Perform validation here.
return (items.Count == 2);
}
}
You will also need to add an Editor attribute to you collection:
class MyClass
{
[Editor(typeof(MyCollectionEditor),
typeof(System.Drawing.Design.UITypeEditor))]
List<Foo> MyCollection
{
get; set;
}
}
NOTE: I found that the value of items in removeButton_Click was not correct - so some tweaking may need to take place.
Try collectionForm.Context.Instance and typecast it to your data type this should do the trick.