how do i use events of RadDragAndDrop in ViewModel - silverlight

I am using telrik RadDragAndDrop Tool with the ListBox. I am using silverlight with mvvm light. My question is that how should I use this code in ViewModel. This is a code behind file.
public Construtor()
{
InitializeComponent();
RadDragAndDropManager.AddDragQueryHandler(this, OnDragQuery);
RadDragAndDropManager.AddDragInfoHandler(this, OnDragInfo);
RadDragAndDropManager.AddDropQueryHandler(this, OnDropQuery);
RadDragAndDropManager.AddDropInfoHandler(this, OnDropInfo);
}
private void OnDropInfo(object sender, DragDropEventArgs e)
{
ItemsControl box = e.Options.Destination as ItemsControl;
IList<Section> itemsSource = box.ItemsSource as IList<Section>;
Section section = e.Options.Payload as Section;
if (e.Options.Status == DragStatus.DropComplete)
{
if (!itemsSource.Contains(section))
{
itemsSource.Add(section);
}
}
}
void OnDropQuery(object sender, DragDropQueryEventArgs e)
{
ItemsControl box = e.Options.Destination as ItemsControl;
IList<Section> itemsSource = box.ItemsSource as IList<Section>;
Section section = e.Options.Payload as Section;
e.QueryResult = section != null && !itemsSource.Contains(section);
}
void OnDragInfo(object sender, DragDropEventArgs e)
{
ListBoxItem listBoxItem = e.Options.Source as ListBoxItem;
ListBox box = ItemsControl.ItemsControlFromItemContainer(listBoxItem) as ListBox;
IList<Section> itemsSource = box.ItemsSource as IList<Section>;
Section section = e.Options.Payload as Section;
if (e.Options.Status == DragStatus.DragComplete)
{
if (section != null && itemsSource.Contains(section))
{
itemsSource.Remove(section);
}
}
}
protected virtual void OnDragQuery(object sender, DragDropQueryEventArgs e)
{
ListBoxItem listBoxItem = e.Options.Source as ListBoxItem;
ListBox box = ItemsControl.ItemsControlFromItemContainer(listBoxItem) as ListBox;
if (e.Options.Status == DragStatus.DragQuery && box != null)
{
e.Options.Payload = box.SelectedItem;
ContentControl cue = new ContentControl();
cue.Content = box.SelectedItem;
e.Options.DragCue = cue;
}
e.QueryResult = true;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
SelectingQuestionsWindow window = new SelectingQuestionsWindow();
window.Show();
this.radExpander1.Visibility = System.Windows.Visibility.Visible;
}
<*XAML*>
This is my Xaml.
<ListBox x:Name="SectionListBoxMain" Height="165" Width="200" SelectedItem="{Binding SelectedSectionList}"
DisplayMemberPath="SectionName" ItemsSource="{Binding SectionList}" ItemContainerStyle="{StaticResource draggableItemStyle}">
<telerik:RadDragAndDropManager.AllowDrop>
true
</telerik:RadDragAndDropManager.AllowDrop>
</ListBox>
<TextBlock Width="20" />
<ListBox x:Name="SectionListBox" Height="167" Width="200" ItemsSource="{Binding SelectedSectionList}" telerik:RadDragAndDropManager.AllowDrop="True" DisplayMemberPath="SectionName" ItemContainerStyle="{StaticResource draggableItemStyle}">
</ListBox>
</StackPanel>

This is view logic that does not really belong in your ViewModel. It is probably better suited to a Behavior.
See this example: http://www.telerik.com/help/silverlight/raddraganddrop-within-radgridview.html
They use a Behavior and attach it to a grid to enable row reordering. You could start with something like:
public partial class ListDragDropBehavior : Behavior<ListBox>
You would need to add some dependency properties to bind to the other list box.
You can then use this Behavior on other list boxes by simply attaching it to the list box (i use blend to attach behaviors)

Related

WPF-MVVM: using ICollectionView for Searching a ListBox

I have a ListBox of Items and a Search TextBox and Search Button, i want to enter the search text in the TextBox and Click Search Button so the ListBox highlight that item and get it on screen (for lengthy list).
Is it possible to do this using ICollectionView? and if not possible how to implement this scenario.
Note: after googling i found all samples talking about Filtering but i need searching.
Thanks for bearing with us.
You can achieve this by implementing a Prism Behavior:
public class AutoScrollingBehavior:Behavior<ListBox>
{
protected override void OnAttached()
{
base.OnAttached();
var itemsSource = AssociatedObject.ItemsSource as ICollectionView;
if (itemsSource == null)
return;
itemsSource.CurrentChanged += ItemsSourceCurrentChanged;
}
void ItemsSourceCurrentChanged(object sender, EventArgs e)
{
AssociatedObject.ScrollIntoView(((ICollectionView)sender).CurrentItem);
AssociatedObject.Focus();
}
}
Another approach is listening to ListBox.SelectionChanged instead of ICollectionView.CurrentChanged.
public class AutoScrollingBehavior:Behavior<ListBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += AssociatedObjectSelectionChanged;
}
void AssociatedObjectSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count <= 0)
return;
AssociatedObject.ScrollIntoView(e.AddedItems[0]);
AssociatedObject.Focus();
}
}
On Xaml:
<ScrollViewer Height="200">
<ListBox x:Name="listbox" ItemsSource="{Binding Path=NamesView}" SelectionMode="Single"
IsSynchronizedWithCurrentItem="True">
<i:Interaction.Behaviors>
<local:AutoScrollingBehavior/>
</i:Interaction.Behaviors>
</ListBox>
</ScrollViewer>
Inside searching command, you set NamesView.MoveCurrentTo(foundItem). However this approach will only scroll to the edge, instead of center, might you expected. If you want it to scroll to the center, you might need ItemContainerGenerator.
In your view model who holds the ICollectionView:
private string _searchText;
public string SearchText
{
get { return _searchText; }
set
{
_searchText = value;
RaisePropertyChanged("SearchText");
}
}
private ICommand _searchCommand;
public ICommand SearchCommand
{
get { return _searchCommand ?? (_searchCommand = new DelegateCommand(Search)); }
}
private void Search()
{
var item = _names.FirstOrDefault(name => name == SearchText);
if (item == null) return;
NamesView.MoveCurrentTo(item);
}
On Xaml, bind TextBox.Text to SearchText and bind search button's Command to SearchCommand.
Hope it can help.

Drag and drop multiple instances of user controls in WPF

The given code works fine with dragging and dropping one instance of control. If I try to drop the same instance again it throws an exception:
Specified element is already the logical child of another element. Disconnect it first.
How do I drop multiple instances of user controls on my Canvas, similar to how Visual Studio toolbox does?
public MainWindow()
{
InitializeComponent();
LoadUsercontrols();
}
private void LoadUsercontrols()
{
List<string> userControlKeys = new List<string>();
userControlKeys.Add("testCtrl1");
userControlKeys.Add("testCtrl2");
Type type = this.GetType();
Assembly assembly = type.Assembly;
foreach (string userControlKey in userControlKeys)
{
userControlFullName = String.Format("{0}.TestControls.{1}", type.Namespace, userControlKey);
UserControl userControl = new UserControl();
userControl = (UserControl)assembly.CreateInstance(userControlFullName);
_userControls.Add(userControlKey, userControl);
}
}
private void TreeViewItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
startPoint = e.GetPosition(null);
}
private void TreeViewItem_PreviewMouseMove(object sender, MouseEventArgs e)
{
// Get the current mouse position
System.Windows.Point mousePos = e.GetPosition(null);
Vector diff = startPoint - mousePos;
if (e.LeftButton == MouseButtonState.Pressed &&
Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance &&
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
{
TreeView treeView = sender as TreeView;
TreeViewItem treeViewItem = FindAnchestor<TreeViewItem>((DependencyObject)e.OriginalSource);
if (treeViewItem != null)
{
Type type = this.GetType();
Assembly assembly = type.Assembly;
DataObject dragData = new DataObject("myFormat", _userControls[((System.Windows.Controls.HeaderedItemsControl)(treeViewItem)).Header.ToString()]);
DragDrop.DoDragDrop(treeViewItem, dragData, DragDropEffects.Copy);
}
}
}
private static T FindAnchestor<T>(DependencyObject current) where T : DependencyObject
{
do
{
if (current is T)
{
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
}
while (current != null);
return null;
}
private void MyDesignerCanvas_DragEnter(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent("myFormat") || sender == e.Source)
{
e.Effects = DragDropEffects.None;
}
}
private void MyDesignerCanvas_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent("myFormat"))
{
if (treeItem != null)
{
UserControl myCanvasItem = e.Data.GetData("myFormat") as UserControl;
UserControl newCanvastItem = new UserControl
{
Content = _userControls[((System.Windows.Controls.HeaderedItemsControl)(treeItem)).Header.ToString()]
};
Point position = e.GetPosition(MyDesignerCanvas);
DesignerCanvas.SetLeft(newCanvastItem, position.X);
DesignerCanvas.SetTop(newCanvastItem, position.Y);
DesignerCanvas.SetZIndex(newCanvastItem, 1);
MyDesignerCanvas.Children.Add(newCanvastItem);
}
}
}
In XAML Code:
<TreeView x:Name="presetTreeView4" Grid.Row="1" >
<TreeViewItem Header="testCtrl1" Selected="TreeViewItem_Selected" PreviewMouseLeftButtonDown="TreeViewItem_PreviewMouseLeftButtonDown" PreviewMouseMove="TreeViewItem_PreviewMouseMove"/>
<TreeViewItem Header="testCtrl2" Selected="TreeViewItem_Selected" PreviewMouseLeftButtonDown="TreeViewItem_PreviewMouseLeftButtonDown" PreviewMouseMove="TreeViewItem_PreviewMouseMove"/>
</TreeView>
<s:DesignerCanvas x:Name="MyDesignerCanvas" AllowDrop="True" Drop="MyDesignerCanvas_Drop" DragEnter="MyDesignerCanvas_DragEnter" Background="#A6B0D2F5" DockPanel.Dock="Bottom" Margin="0" >
</s:DesignerCanvas>
You cannot add the same control to different containers - a control can only appear once in the visual tree.
Instead of loading the user controls in advance, you should construct them at MyDesignerCanvas_Drop (i.e. use Activator the same way you're using it right now in LoadUsercontrols) and assign the resulting control to the UserControl.Content.
I think you have to clone control _userControls[((System.Windows.Controls.HeaderedItemsControl)(treeItem)).Header.ToString()] in MyDesignerCanvas_Drop

Accessing to a ListView at runtime to update an item

I have to update a ListView item by clicking on a button. How do I find and update one at the runtime?
update: I mean I have to find the certain ListView item and update the text of this item only.
When ListViewItems were added to the ListView manually, you can look them up by their content and replace with new content like this (using System.Linq):
object contentToReplace = ...;
object newContent = ...;
ListViewItem item = listView.Items.Cast<ListViewItem>().FirstOrDefault(
lvi => lvi.Content == contentToReplace);
if (item != null)
{
item.Content = newContent;
}
You may use commands. For example:
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public static readonly ICommand ItemClickCommand = new RoutedCommand("ItemClick", typeof(MainWindow));
public MainWindow()
{
InitializeComponent();
this.CommandBindings.Add(
new CommandBinding(
MainWindow.ItemClickCommand,
this.ExecuteItemClickCommand,
this.CanExecuteItemClickCommand));
}
private void CanExecuteItemClickCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = e.Parameter is ListBoxItem;
}
private void ExecuteItemClickCommand(object sender, ExecutedRoutedEventArgs e)
{
// Here you can access ListBoxItem that holds a clicked button.
ListBoxItem listBoxItem = (ListBoxItem)e.Parameter;
listBoxItem.Content = "...";
}
}
}
Now, the only thing you need is to assign ItemClickCommand to a button and bind CommandParameter to corresponding ListBoxItem.
XAML example:
<Window ...
xmlns:local="clr-namespace:WpfApplication1">
<ListBox>
<ListBoxItem>
<ListBoxItem.Content>
<Button Command="{x:Static local:MainWindow.ItemClickCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Content="Click Me"/>
</ListBoxItem.Content>
<...>

WPF DataGrid Hide RowDetails or Unselect Row

I have a DataGrid who's RowDetails is set to show when selected (RowDetailsVisibilityMode="VisibleWhenSelected"). Now I want to be able to get rid of it! I put a close button on the row details with this code:
private void Button_Click(object sender, RoutedEventArgs e)
{
e.Handled = true;
Button button = sender as Button;
DataGridRow row = button.FindAncestor<DataGridRow>();
row.DetailsVisibility = Visibility.Collapsed;
}
That code gets me 90% there, but once the row details is collapsed for a given row it will not appear the next time that row is selected.
I've run into this too. Here's a solution:
Keep that button in the RowDetails and change its code a little. Rather than focusing on the individual row's visibility, set the DataGrid's SelectedIndex property to -1 (none selected).
DataGrid1.SelectedIndex = -1;
Since your RowDetailsVisibilityMode is VisibleWhenSelected, the DataGrid will collapse/hide any expanded RowDetails. This works well when the SelectionMode is Single.
You can implement this with the following code in XAML:
<WpfToolkit:DataGrid Name="dgSysthetic" ItemsSource="{Binding}"
AutoGenerateColumns="True"
SelectionMode="Extended"
RowDetailsVisibilityMode="Collapsed"
CanUserAddRows="False" CanUserDeleteRows="False"
CanUserResizeRows="False" CanUserSortColumns="False"
RowHeaderWidth="20" RowHeight="25">
<WpfToolkit:DataGrid.RowHeaderTemplate>
<DataTemplate>
<Button Name="btnHideRow" Click="btnHideDetails_Click" FontSize="5">></Button>
</DataTemplate>
</WpfToolkit:DataGrid.RowHeaderTemplate>
<WpfToolkit:DataGrid.RowDetailsTemplate>
<DataTemplate>
<WpfToolkit:DataGrid Name="dgAnalytical" ItemsSource="{Binding}" AutoGenerateColumns="True"/>
</DataTemplate>
</WpfToolkit:DataGrid.RowDetailsTemplate>
</WpfToolkit:DataGrid>
See the button inside RowHeaderTemplate.
In your C# code you would do this:
private void btnHideDetails_Click(object sender, RoutedEventArgs e)
{
DependencyObject obj = (DependencyObject)e.OriginalSource;
while (!(obj is DataGridRow) && obj != null) obj = VisualTreeHelper.GetParent(obj);
if (obj is DataGridRow)
{
if ((obj as DataGridRow).DetailsVisibility == Visibility.Visible)
{
(obj as DataGridRow).DetailsVisibility = Visibility.Collapsed;
}
else
{
(obj as DataGridRow).DetailsVisibility = Visibility.Visible;
}
}
}
This worked very well for me.
you can put this as your buttons click event, it walks up the tree finds the datarow and sets the details where needed.
DependencyObject dep = (DependencyObject)e.OriginalSource;
while ((dep != null) && !(dep is DataGridRow))
{
dep = VisualTreeHelper.GetParent(dep);
}
if (dep != null && dep is DataGridRow)
{
DataGridRow row = dep as DataGridRow;
if (row.DetailsVisibility == Visibility.Collapsed)
{
row.DetailsVisibility = Visibility.Visible;
}
else
{
row.DetailsVisibility = Visibility.Collapsed;
}
}
try adding row.DetailsVisibility = Visibility.Visible; on the RowDetailsVisibilityChanged event.
Try setting a Style on the Button with Setters that set the Button's Command, CommandParameter properties. You'll need to create your a class that inplements ICommand and include it as a StaticResource in XAML. Here I used the DataGridRowHeader as a button instead of a button within row details.
<local:DeselectRowCommand x:Key='deselectCommand' />
<Setter Property='Command' Value='{StaticResource deselectCommand}' />
<Setter Property='CommandParameter'
Value='{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=wpf:DataGridRow}}' />
In the command's Execute method you can get the DataGridRow from the command parameter and apply whatever methods you need to.
At least this way you can share this Style or base others off of it and re-use the ICommand for your other DataGrids, and also less event handling.
You can see a working example in this Silverlight-to-WPF DataGrid open-source project.
Try this (adding the PreviewMouseDown event to your DataGrid in XAML):
private void UIElement_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
DataGrid grid = sender as DataGrid;
if (grid != null)
{
FrameworkElement element = e.OriginalSource as FrameworkElement;
if (element?.DataContext is FixedIncomeOrder)
{
if (grid.SelectedItem == (FixedIncomeOrder) ((FrameworkElement) e.OriginalSource).DataContext)
{
grid.SelectedIndex = -1;
e.Handled = true;
}
}
}
}
Make sure your datagrid has a name such as
<DataGrid x:Name="dgPrimary"
...>
Place a button in the row template such as
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Button Content="X" Click="Button_Click" Width="20"/>
....
Then in the codebehind simply set the datagrid's selected index to -1
private void Button_Click(object sender, RoutedEventArgs e)
{
dgPrimary.SelectedIndex = -1;
}

Is there Selected Tab Changed Event in the standard WPF Tab Control

In WPF, is there an event that can be used to determine when a TabControl's selected tab changes?
I have tried using TabControl.SelectionChanged but it is getting fired many times when a child's selection within a tab is changed.
I tied this in the handler to make it work:
void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.Source is TabControl)
{
//do work when tab is changed
}
}
If you set the x:Name property to each TabItem as:
<TabControl x:Name="MyTab" SelectionChanged="TabControl_SelectionChanged">
<TabItem x:Name="MyTabItem1" Header="One"/>
<TabItem x:Name="MyTabItem2" Header="2"/>
<TabItem x:Name="MyTabItem3" Header="Three"/>
</TabControl>
Then you can access to each TabItem at the event:
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (MyTabItem1.IsSelected)
// do your stuff
if (MyTabItem2.IsSelected)
// do your stuff
if (MyTabItem3.IsSelected)
// do your stuff
}
If you just want to have an event when a tab is selected, this is the correct way:
<TabControl>
<TabItem Selector.Selected="OnTabSelected" />
<TabItem Selector.Selected="OnTabSelected" />
<TabItem Selector.Selected="OnTabSelected" />
<!-- You can also catch the unselected event -->
<TabItem Selector.Unselected="OnTabUnSelected" />
</TabControl>
And in your code
private void OnTabSelected(object sender, RoutedEventArgs e)
{
var tab = sender as TabItem;
if (tab != null)
{
// this tab is selected!
}
}
You could still use that event. Just check that the sender argument is the control you actually care about and if so, run the event code.
If you're using the MVVM pattern then it is inconvenient (and breaks the pattern) to use the event handler. Instead, you can bind each individual TabItem's Selector.IsSelected property to a dependency property in your viewmodel and then handle the PropertyChanged event handler. That way you know exactly which tab was selected/deselected based on the PropertyName and you have a special handler for each tab.
Example: MainView.xaml
<TabControl>
<TabItem Header="My tab 1" Selector.IsSelected="{Binding IsMyTab1Selected}"> ... </TabItem>
<TabItem Header="My tab 2" Selector.IsSelected="{Binding IsMyTab2Selected}"> ... </TabItem>
</TabControl>
Example: MainViewModel.cs
public bool IsMyTab1Selected {
get { return (bool)GetValue(IsMyTab1SelectedProperty); }
set { SetValue(IsMyTab1SelectedProperty, value); }
}
public static readonly DependencyProperty IsMyTab1SelectedProperty =
DependencyProperty.Register("IsMyTab1Selected", typeof(bool), typeof(MainViewModel), new PropertyMetadata(true, new PropertyChangedCallback(MyPropertyChanged)));
public bool IsMyTab2Selected {
get { return (bool)GetValue(IsMyTab2SelectedProperty); }
set { SetValue(IsMyTab2SelectedProperty, value); }
}
public static readonly DependencyProperty IsMyTab2SelectedProperty =
DependencyProperty.Register("IsMyTab2Selected", typeof(bool), typeof(MainViewModel), new PropertyMetadata(false, new PropertyChangedCallback(MyPropertyChanged)));
private void MyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (e.Property.Name == "IsMyTab1Selected") {
// stuff to do
} else if (e.Property.Name == "IsMyTab2Selected") {
// stuff to do
}
}
If your MainViewModel is INotifyPropertyChanged rather than DependencyObject, then use this instead:
Example: MainViewModel.cs
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public MainViewModel() {
PropertyChanged += handlePropertyChanged;
}
public bool IsMyTab1Selected {
get { return _IsMyTab1Selected ; }
set {
if (value != _IsMyTab1Selected ) {
_IsMyTab1Selected = value;
OnPropertyChanged("IsMyTab1Selected ");
}
}
}
private bool _IsMyTab1Selected = false;
public bool IsMyTab2Selected {
get { return _IsMyTab2Selected ; }
set {
if (value != _IsMyTab2Selected ) {
_IsMyTab2Selected = value;
OnPropertyChanged("IsMyTab2Selected ");
}
}
}
private bool _IsMyTab2Selected = false;
private void handlePropertyChanged(object sender, PropertyChangedEventArgs e) {
if (e.PropertyName == "IsMyTab1Selected") {
// stuff to do
} else if (e.PropertyName == "IsMyTab2Selected") {
// stuff to do
}
}
The event generated is bubbling up until it is handled.
This xaml portion below triggers ui_Tab_Changed after ui_A_Changed when the item selected in the ListView changes, regardless of TabItem change in the TabControl.
<TabControl SelectionChanged="ui_Tab_Changed">
<TabItem>
<ListView SelectionChanged="ui_A_Changed" />
</TabItem>
<TabItem>
<ListView SelectionChanged="ui_B_Changed" />
</TabItem>
</TabControl>
We need to consume the event in ui_A_Changed (and ui_B_Changed, and so on):
private void ui_A_Changed(object sender, SelectionChangedEventArgs e) {
// do what you need to do
...
// then consume the event
e.Handled = true;
}
That is the correct event. Maybe it's not wired up correctly?
<TabControl SelectionChanged="TabControl_SelectionChanged">
<TabItem Header="One"/>
<TabItem Header="2"/>
<TabItem Header="Three"/>
</TabControl>
in the codebehind....
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
int i = 34;
}
if I set a breakpoint on the i = 34 line, it ONLY breaks when i change tabs, even when the tabs have child elements and one of them is selected.
This code seems to work:
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
TabItem selectedTab = e.AddedItems[0] as TabItem; // Gets selected tab
if (selectedTab.Name == "Tab1")
{
// Do work Tab1
}
else if (selectedTab.Name == "Tab2")
{
// Do work Tab2
}
}
If anyone use WPF Modern UI,they cant use OnTabSelected event.but they can use SelectedSourceChanged event.
like this
<mui:ModernTab Layout="Tab" SelectedSourceChanged="ModernTab_SelectedSourceChanged" Background="Blue" AllowDrop="True" Name="tabcontroller" >
C# code is
private void ModernTab_SelectedSourceChanged(object sender, SourceEventArgs e)
{
var links = ((ModernTab)sender).Links;
var link = this.tabcontroller.Links.FirstOrDefault(l => l.Source == e.Source);
if (link != null) {
var index = this.tabcontroller.Links.IndexOf(link);
MessageBox.Show(index.ToString());
}
}

Resources