I have a combobox which will load data in the data grid based on selection change.
On change of combobox selection i need to check if the current data in data grid is correct or not . if not correct i would like to cancel the combobox selection change.
here is my behavior class
public class ComboBoxSelectionBehaviour : Behavior<ComboBox>
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached(
"Source",
typeof(ViewModel),
typeof(ComboBoxSelectionBehaviour),
new PropertyMetadata(null));
public ViewModel Source
{
get { return (ViewModel)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged; ;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SelectionChanged -= AssociatedObject_SelectionChanged;
}
private void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var combo = sender as ComboBox;
if (Source != null)
{
// Suppress the event if errors exist
if (!Source.IsDataCorrect())
{
e.Handled = true;
}
}
}
}
even after handling the event combobox selected item is getting changed.
Please give some suggestions to solve this issue.
Could you simply just do a
myComboBox.SelectedIndex--
If the data is not correct? Or would this cause an infinite loop?
Related
I am creating custom transport controls.
I want to have a Visibility control for a custom Button which I have created. So I have created a Property for it. In that Property, I have used GetTemplateChild("CompactOverlayButton") as Button to get the particular button but it returns null.
Here is my code
public bool IsCompactOverlayButtonVisible
{
get
{
var compactOverlayButton = GetTemplateChild("CompactOverlayButton") as Button;
if (compactOverlayButton.Visibility == Visibility.Visible)
return true;
else
return false;
}
set
{
var compactOverlayButton = GetTemplateChild("CompactOverlayButton") as Button;
if (value)
compactOverlayButton.Visibility = Visibility.Visible;
else
compactOverlayButton.Visibility = Visibility.Collapsed;
}
}
But the same line of code returns proper value in OnApplyTemplate() function.
Here is my code for OnApplyTemplate()
protected override void OnApplyTemplate()
{
var compactOverlayButton = GetTemplateChild("CompactOverlayButton") as Button;
compactOverlayButton.Click += CompactOverlayButton_ClickAsync;
base.OnApplyTemplate();
}
IsCompactOverlayButtonVisible probably gets evaluated for the first time before OnApplyTemplate(), meaning that the first time it gets evaluated, the template hasn't been applied and the button doesn't exist yet. In OnApplyTemplate(), get the button and assign it to a private field.
private Button _compactOverlayButton;
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
_compactOverlayButton = GetTemplateChild("CompactOverlayButton") as Button;
_compactOverlayButton.Click += CompactOverlayButton_ClickAsync;
}
And before you try to touch the button's properties, make sure it's not null.
public bool IsCompactOverlayButtonVisible
{
get
{
return _compactOverlayButton != null
&& _compactOverlayButton.Visibility == Visibility.Visible;
}
set
{
if (_compactOverlayButton != null)
{
compactOverlayButton.Visibility = value
? Visibility.Visible
: Visibility.Collapsed;
}
}
}
If something will set this value before the template is applied, for example if it's a public property of the control that may be set in XAML (which it sure looks like it is), you can't do it this way. You need to make it a regular dependency property, give it a PropertyChanged handler that updates the button's visibility if the button exists, and add a line in OnApplyTemplate() to update the actual button when you get your hands on it. Then it'll be usable as a target of a binding as well.
Update
And here's how you do that. This is the right way.
private Button _compactOverlayButton;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_compactOverlayButton = GetTemplateChild("CompactOverlayButton") as Button;
// Update actual button visibility to match whatever the dependency property value
// is, in case XAML gave us a value for it already.
OnIsCompactOverlayButtonVisibleChanged();
_compactOverlayButton.Click += CompactOverlayButton_Click;
// Secondly, just in case something in the XAML may change the button's visibility,
// put a watch on the property and update our dependency property to match when that
// changes.
var dpd = DependencyPropertyDescriptor.FromProperty(Button.VisibilityProperty, typeof(Button));
dpd.AddValueChanged(_compactOverlayButton, CompactOverlayButton_VisibilityChanged);
}
protected void CompactOverlayButton_VisibilityChanged(object sender, EventArgs args)
{
IsCompactOverlayButtonVisible = _compactOverlayButton.Visibility == Visibility.Visible;
}
private void CompactOverlayButton_Click(object sender, RoutedEventArgs e)
{
// ...whatever
}
#region IsCompactOverlayButtonVisible Property
public bool IsCompactOverlayButtonVisible
{
get { return (bool)GetValue(IsCompactOverlayButtonVisibleProperty); }
set { SetValue(IsCompactOverlayButtonVisibleProperty, value); }
}
public static readonly DependencyProperty IsCompactOverlayButtonVisibleProperty =
DependencyProperty.Register(nameof(IsCompactOverlayButtonVisible), typeof(bool), typeof(CustomMediaTransportControls),
new FrameworkPropertyMetadata(true,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
IsCompactOverlayButtonVisible_PropertyChanged));
protected static void IsCompactOverlayButtonVisible_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// It's a hassle to do stuff in a static method, so my dependency property
// snippet just creates a private instance method and calls it from the
// static handler.
(d as CustomMediaTransportControls).OnIsCompactOverlayButtonVisibleChanged();
}
private void OnIsCompactOverlayButtonVisibleChanged()
{
if (_compactOverlayButton != null)
{
// If the existing value is the same as the new value, this is a no-op
_compactOverlayButton.Visibility =
IsCompactOverlayButtonVisible
? Visibility.Visible
: Visibility.Collapsed;
}
}
#endregion IsCompactOverlayButtonVisible Property
Again a have a (probably) simple Problem.
I would like to create a custom UIElement (A Collection of Lines which stay orthogonal).
This UIElement is used as View in my MVVM application.
Here is my code:
class RaOrthogonalLine : Canvas, INotifyPropertyChanged
{
public RaOrthogonalLine()
{
Points.CollectionChanged += Points_CollectionChanged;
}
void Points_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Paint();
}
void Paint()
{
//PaintingStuff! Here I would like to get in!
}
void newLine_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (LineClicked != null)
LineClicked(sender, e);
}
public delegate void LineClickedEventHandler(object sender, MouseButtonEventArgs e);
public event LineClickedEventHandler LineClicked;
public ObservableCollection<RaPoint> Points
{
get
{
return (ObservableCollection<RaPoint>)GetValue(PointsProperty);
}
set
{
SetValue(PointsProperty, value);
RaisePropertyChanged("Points");
}
}
public static readonly DependencyProperty PointsProperty = DependencyProperty.Register("Points", typeof(ObservableCollection<RaPoint>), typeof(RaOrthogonalLine),
new FrameworkPropertyMetadata(new ObservableCollection<RaPoint>(), new PropertyChangedCallback(PointsPropertyChanged))
{
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
}
);
private static void PointsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RaOrthogonalLine thisLine = (RaOrthogonalLine)d;
thisLine.Paint();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
In my XAML I bind a ObservableCollection of my ViewModel to my ObservableCollection in the UIElement(View).
That works fine.
My problem now is that I do not get notified when the Collection changes (Add/Remove/..) - because then i would need to Repaint it.
I tried to get the Points.CollectionChanged event but it does not fire.
Has anyone a idea?
Thank you!
The problem is that you are adding the CollectionChanged Event handler in the constructor of your Control. In the constructor your Paint property is not binded to the right source yet (Indeed it has the PointsProperty's default value, i.e. an empty collection).
You should add and remove the event handler in the PointsPropertyChanged method. Take a look to this sample code:
public class RaOrthogonalLine : Canvas
{
public INotifyCollectionChanged Points
{
get { return (INotifyCollectionChanged)GetValue(PointsProperty); }
set { SetValue(PointsProperty, value); }
}
public static readonly DependencyProperty PointsProperty =
DependencyProperty.Register("Points", typeof(INotifyCollectionChanged), typeof(RaOrthogonalLine),
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(PointsPropertyChanged))
{
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
void Points_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Paint();
}
private static void PointsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RaOrthogonalLine raOrthogonalLine = (RaOrthogonalLine)d;
INotifyCollectionChanged newValue = (INotifyCollectionChanged)e.NewValue;
INotifyCollectionChanged oldValue = (INotifyCollectionChanged)e.OldValue;
if (oldValue != null)
{
oldValue.CollectionChanged -= raOrthogonalLine.Points_CollectionChanged;
}
if (newValue != null)
{
newValue.CollectionChanged += raOrthogonalLine.Points_CollectionChanged;
}
raOrthogonalLine.Paint();
}
}
I hope it can help you with your problem.
It might have to do with dependency property but this works just fine for me.
Are you sure you are adding to and removing from the collection (not replacing the collection)?
public class Points
{
void Strings_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Debug.WriteLine("Strings_CollectionChanged");
}
private ObservableCollection<string> strings = new ObservableCollection<string>();
// I think you are better off with just a get
public ObservableCollection<string> Strings { get { return strings; } }
public Points()
{
Strings.CollectionChanged += new NotifyCollectionChangedEventHandler(Strings_CollectionChanged);
Strings.Add("new one");
Strings.Add("new two");
Strings.RemoveAt(0);
}
}
I want to select programatically multiple items in my ListBox. So, to be as mvvm friendly as possible, I create a custom control inherited from ListBox. In this custom control I've made a dependency property allowing items selection changes. Here is the code of the OnPropertyChanged part :
private static void OnSetSelectionToPropertyChanged
(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
InitializableListBox list = d as InitializableListBox;
Dictionary<int, string> toSelect = e.NewValue as Dictionary<int, string>;
if (toSelect == null)
return;
list.SetSelectedItems(toSelect);
}
The selection works great, but this solution does not raise the OnSelectionChanged event
So I try also :
private static void OnSetSelectionToPropertyChanged
(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
InitializableListBox list = d as InitializableListBox;
Dictionary<int, string> toSelect = e.NewValue as Dictionary<int, string>;
if (toSelect == null)
return;
SelectionChangedEventArgs e_selChanged;
List<Object> removed = new List<object>();
List<Object> added = new List<object>();
//Clear the SelectedItems list
while(list.SelectedItems.Count > 0)
{
removed.Add(list.SelectedItems[0]);
list.SelectedItems.RemoveAt(0);
}
//Add each selected items
foreach (var item in toSelect)
{
list.SelectedItems.Add(item);
added.Add(list.SelectedItems[list.SelectedItems.Count - 1]);
}
//Raise the SelectionChanged event
e_selChanged = new SelectionChangedEventArgs(SelectionChangedEvent,removed,added);
list.OnSelectionChanged(e_selChanged);
}
But this was not better. I think I'm not dealing the right way with the event so if you could help me, that would be great.
Thanks in advance.
EDIT
I have found another solution (more a hack actually) than #NETscape. I don't think it's better, but it seems to work pretty well, and it's maybe easier.
The trick is to made a dependency property wich allow you to access the SelectedItems property (wich is readonly and unbindable on the normal ListBox). Here is the code of my custom ListBox :
public class InitializableListBox : ListBox
{
public InitializableListBox()
{
SelectionChanged += CustomSelectionChanged;
}
private void CustomSelectionChanged(object sender, SelectionChangedEventArgs e)
{
InitializableListBox s = sender as InitializableListBox;
if (s == null)
return;
s.CustomSelectedItems = s.SelectedItems;
}
#region CustomSelectedItems DependyProperty
public static DependencyProperty CustomSelectedItemsProperty =
DependencyProperty.RegisterAttached("CustomSelectedItems",
typeof(System.Collections.IList),typeof(InitializableListBox),
new PropertyMetadata(null, OnCustomSelectedItemsPropertyChanged));
public System.Collections.IList CustomSelectedItems
{
get
{
return (System.Collections.IList)GetValue(CustomSelectedItemsProperty);
}
set
{
SetValue(CustomSelectedItemsProperty, value);
}
}
private static void OnCustomSelectedItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
InitializableListBox list = d as InitializableListBox;
System.Collections.IList toSelect = e.NewValue as System.Collections.IList;
if (toSelect == null)
return;
list.SetSelectedItems(toSelect);
}
#endregion
}
See this
If you set your ItemsSource to a collection, and the objects in the collection implement INPC, you can set the Style on ListViewItem to use the bound objects IsSelected property.
See this answer to understand what I mean.
Let's say you have ItemsSource="{Binding Items}", then you can do something like:
Items.Where(item => item.IsSelected == true);
to return your list of items that are selected.
You could also do Items[0].IsSelected = true; to programmatically select an item.
In short, you shouldn't have to use a custom control to implement multiple selection on a ListView.
EDIT
I experienced the virtualization problem when I was implementing Telerik's RadGridView. I used a behavior to counter this problem and it seems to have worked:
public class RadGridViewExt : Behavior<RadGridView>
{
private RadGridView Grid
{
get
{
return AssociatedObject as RadGridView;
}
}
public INotifyCollectionChanged SelectedItems
{
get { return (INotifyCollectionChanged)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(INotifyCollectionChanged), typeof(RadGridViewExt), new PropertyMetadata(OnSelectedItemsPropertyChanged));
private static void OnSelectedItemsPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
{
var collection = args.NewValue as INotifyCollectionChanged;
if (collection != null)
{
collection.CollectionChanged += ((RadGridViewExt)target).ContextSelectedItemsCollectionChanged;
}
}
protected override void OnAttached()
{
base.OnAttached();
Grid.SelectedItems.CollectionChanged += GridSelectedItemsCollectionChanged;
}
void ContextSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UnsubscribeFromEvents();
Transfer(SelectedItems as IList, AssociatedObject.SelectedItems);
SubscribeToEvents();
}
void GridSelectedItemsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UnsubscribeFromEvents();
Transfer(AssociatedObject.SelectedItems, SelectedItems as IList);
SubscribeToEvents();
}
private void SubscribeToEvents()
{
AssociatedObject.SelectedItems.CollectionChanged += GridSelectedItemsCollectionChanged;
if (SelectedItems != null)
{
SelectedItems.CollectionChanged += ContextSelectedItemsCollectionChanged;
}
}
private void UnsubscribeFromEvents()
{
AssociatedObject.SelectedItems.CollectionChanged -= GridSelectedItemsCollectionChanged;
if (SelectedItems != null)
{
SelectedItems.CollectionChanged -= ContextSelectedItemsCollectionChanged;
}
}
public static void Transfer(IList source, IList target)
{
if (source == null || target == null)
return;
target.Clear();
foreach (var o in source)
{
target.Add(o);
}
}
}
Inside RadGridView control:
<i:Interaction.Behaviors>
<local:RadGridViewExt SelectedItems="{Binding SelectedItems}" />
</i:Interaction.Behaviors>
where
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
and remember to add reference to Interactivity assembly.
ListBox has a property called SelectionMode. You can set it to Multiple to enable multiple selection.
<ListBox x:Name="MyListBox" SelectionMode="Multiple"/>
You can use SelectedItems property to get all the selected items afterwards.
I am working on a datagrid which enable user to select multiple rows. but when user clicks on header of row the selection get lost.
How can I disable the row selection on right click if the row is already selected.
I tried to do it via Behavior
public class DataGridRowBehavior
{
public static readonly DependencyProperty DisableSelectionOnRightClickProperty = DependencyProperty.RegisterAttached(
"DisableSelectionOnRightClick",
typeof(bool),
typeof(DataGridRowBehavior),
new UIPropertyMetadata(false, OnDisableSelectionOnRightClick));
public static bool GetDisableSelectionOnRightClick(DependencyObject dgRow)
{
return (bool)dgRow.GetValue(DisableSelectionOnRightClickProperty);
}
public static void SetDisableSelectionOnRightClick(DependencyObject dgRow, bool value)
{
dgRow.SetValue(DisableSelectionOnRightClickProperty, value);
}
public static void SetListViewFocus(DependencyObject d, bool use)
{
d.SetValue(DisableSelectionOnRightClickProperty, use);
}
public static void OnDisableSelectionOnRightClick(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DataGridRowHeader header = d as DataGridRowHeader;
header.MouseRightButtonUp += header_MouseRightButtonUp;
}
static void header_MouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var header = sender as DataGridRowHeader;
if (header.IsRowSelected)
{
if (header.ContextMenu != null)
{
header.ContextMenu.IsOpen = true;
}
e.Handled = true;
}
}
}
But this one is not working correctly as other functionality of right click is also broken. e.g Context menu. The context menu is not enabling its Application Commands.
Is there any other way to disable the selection or let the selection remain as it is if I click on any of the selected row?
You have two choices:
Either create custom DataGrid or DataGridRow and create SelectionChanging or Selection events. It's needed to prevent selection. This time, this controls have only SelectionChanged and Selected events. Next time, I think you could write code
If you do not want create custom controls you can create a Behavior. For example:
public class SuppressButtonClickBehavior : Behavior<Button>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.AddHandler(UIElement.PreviewMouseLeftButtonDownEvent,
new RoutedEventHandler(OnPreviewMouseLeftButtonDown),
true);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.RemoveHandler(UIElement.PreviewMouseLeftButtonDownEvent,
new RoutedEventHandler(OnPreviewMouseLeftButtonDown));
}
private void OnPreviewMouseLeftButtonDown(Object sender, RoutedEventArgs e)
{
e.Handled = true;
if (AssociatedObject.Command != null)
{
AssociatedObject.Command.Execute(AssociatedObject.CommandParameter);
}
}
}
If you want, you can make this code more flexible. But you must understand, that you can only set e.Handled to true to prevent selection.
I developed my UI on MVVM pattern and now stuck on getting SelectedItems. Could you please modify my XAML and provide sample how do I get them insde ViewModel class.
<xcdg:DataGridControl Name="ResultGrid" ItemsSource="{Binding Results}" Height="295" HorizontalAlignment="Left" Margin="6,25,0,0" VerticalAlignment="Top" Width="1041" ReadOnly="True">
<xcdg:DataGridControl.View>
<xcdg:TableflowView UseDefaultHeadersFooters="False">
<xcdg:TableflowView.FixedHeaders>
<DataTemplate>
<xcdg:ColumnManagerRow />
</DataTemplate>
</xcdg:TableflowView.FixedHeaders>
</xcdg:TableflowView>
</xcdg:DataGridControl.View>
</xcdg:DataGridControl>
You can use Attached behaviors to get/set SelectedItems to datagrid.
I was facing similar issue in Metro apps, So had to write it myself.
Below is the link
http://www.codeproject.com/Articles/412417/Managing-Multiple-selection-in-View-Model-NET-Metr
Though i had written for metro apps, the same solution can be adapted in WPF/Silverlight.
public class MultiSelectBehavior : Behavior<ListViewBase>
{
#region SelectedItems Attached Property
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
"SelectedItems",
typeof(ObservableCollection<object>),
typeof(MultiSelectBehavior),
new PropertyMetadata(new ObservableCollection<object>(), PropertyChangedCallback));
#endregion
#region private
private bool _selectionChangedInProgress; // Flag to avoid infinite loop if same viewmodel is shared by multiple controls
#endregion
public MultiSelectBehavior()
{
SelectedItems = new ObservableCollection<object>();
}
public ObservableCollection<object> SelectedItems
{
get { return (ObservableCollection<object>)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += OnSelectionChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SelectionChanged -= OnSelectionChanged;
}
private static void PropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
NotifyCollectionChangedEventHandler handler = (s, e) => SelectedItemsChanged(sender, e);
if (args.OldValue is ObservableCollection<object>)
{
(args.OldValue as ObservableCollection<object>).CollectionChanged -= handler;
}
if (args.NewValue is ObservableCollection<object>)
{
(args.NewValue as ObservableCollection<object>).CollectionChanged += handler;
}
}
private static void SelectedItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (sender is MultiSelectBehavior)
{
var listViewBase = (sender as MultiSelectBehavior).AssociatedObject;
var listSelectedItems = listViewBase.SelectedItems;
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
if (listSelectedItems.Contains(item))
{
listSelectedItems.Remove(item);
}
}
}
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
if (!listSelectedItems.Contains(item))
{
listSelectedItems.Add(item);
}
}
}
}
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_selectionChangedInProgress) return;
_selectionChangedInProgress = true;
foreach (var item in e.RemovedItems)
{
if (SelectedItems.Contains(item))
{
SelectedItems.Remove(item);
}
}
foreach (var item in e.AddedItems)
{
if (!SelectedItems.Contains(item))
{
SelectedItems.Add(item);
}
}
_selectionChangedInProgress = false;
}
}
There is probably more to do if you want a multiselection and you want to get those selected items. Do you want to store the selected items and when some action is performed (button clicked or something like that) you want to use those selectedItems and do something with them?
There is a good example on that available here:
Get SelectedItems From DataGrid Using MVVM
It states it is designed for Silverlight, but it will work in WPF with MVVM too.
Perhaps this is a more straightforward approach:
Get Selected items in a WPF datagrid
Creating an attached behavior for wiring up every Read Only Collection or non Dependency property would take a significant amount of work. A simple solution is to pass the reference to the view model using the view.
Private ReadOnly Property ViewModel As MyViewModel
Get
Return DirectCast(DataContext, MyViewModel)
End Get
End Property
Private Sub MyView_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
If ViewModel.SelectedItems Is Nothing Then
ViewModel.SelectedItems = MyDataGrid.SelectedItems
End If
End Sub