Selected item as Current Item in c# - c#

I have a list box which is bound to a collection in C# WPF. When I search for a record I want to move the selected item to the top of the list and mark as selected.
Here is my code:
var loc = lst_sub.Items.IndexOf(name);
lst_sub.SelectedIndex = loc;
lst_sub.Items.MoveCurrentToFirst();

This can be handled using a Behavior class ...
public class perListBoxHelper : Behavior<ListBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
protected override void OnDetaching()
{
AssociatedObject.SelectionChanged -= AssociatedObject_SelectionChanged;
base.OnDetaching();
}
private static void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = sender as ListBox;
if (listBox?.SelectedItem == null)
{
return;
}
Action action = () =>
{
listBox.UpdateLayout();
if (listBox.SelectedItem == null)
{
return;
}
listBox.ScrollIntoView(listBox.SelectedItem);
};
listBox.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
}
}
Usage ...
<ListBox
Width="200"
Height="200"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}">
<i:Interaction.Behaviors>
<vhelp:perListBoxHelper />
</i:Interaction.Behaviors>
</ListBox>
More details on my blog post.

Related

How can i make sure that my property's setter will get executed in this scenario?

I have the following codes:
One of my viewmodel's properties:
private ObservableCollection<ArukeresoShopMediator<T>> _shops;
public ObservableCollection<ArukeresoShopMediator<T>> Shops { ... }
ArukeresoShopMediator class:
class ArukeresoShopMediator<T>
where T : CheckArukeresoArCriteriaType, INotifyPropertyChanged
{
public Shop Shop { ... }
private T _data;
public T Data { ... }
public string Key { ... }
public string Name { ... }
private bool _checked;
public bool Checked { ... }
public ArukeresoShopMediator(T data, Shop shop) { ... }
}
UI part of the code:
<ListBox Grid.Row="2"
Grid.ColumnSpan="2"
Visibility="{Binding CompaniesVisibility}"
ItemsSource="{Binding Shops, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Checked}" Content="{Binding}"></CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
My problem is that there's a function that i'd like to execute within my viewmodel's Shops property's setter when the Checkbox property is changed but currently i have no idea how to do that.
As you can see Shops property is an ObservableCollection of type ArukeresoShopMediator which has a boolean Checked property. How can i make my Shops property respond to the Checked property's changes?
As i mentioned in the comments above i needed something like the post mentioned by Sinatr, so i found a working solution and it looks like this:
void items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
foreach (ArukeresoShopMediator<T> item in e.OldItems)
{
item.PropertyChanged -= item_PropertyChanged;
}
}
if (e.NewItems != null)
{
foreach (ArukeresoShopMediator<T> item in e.NewItems)
{
item.PropertyChanged += item_PropertyChanged;
}
}
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
...
}
public ArukeresoCriteriaTypeInspectorViewModelBase(T data)
: base(data)
{
...
Shops = new ObservableCollection<ArukeresoShopMediator<T>>();
Shops.CollectionChanged += items_CollectionChanged;
...

How do I get the text value of the selected TreeViewItem?

My code that generates the TreeView from the database is working, but now I want to open new window based on the selected TreeViewItem but I am not sure how to access the item in my SelectedItemChanged event handler.
TreeView Xaml:
<TreeView x:Name="Tree" SelectedItemChanged="Tree_SelectedItemChanged" ItemsSource="{Binding RootNodes}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding rsParentChild}">
<Grid>
<materialDesign:PackIcon Kind="{Binding icon}" VerticalAlignment="Center"/>
<TextBlock Name="MenuItem" Margin="20,0,0,0" Text="{Binding NodeDescription}" FontFamily="Humanst521 Lt BT" ></TextBlock>
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The code behind:
public Dashboard()
{
InitializeComponent();
this.DataContext = dbm;
dbm = (Model.Administration.DashboardModel)FindResource("modVar");
var dataSet = getData();
_rootNodes = dataSet.Tables["Table1"].DefaultView;
_rootNodes.RowFilter = "ParentId IS NULL";
this.DataContext = this;
}
private DataView _rootNodes;
public DataView RootNodes
{
get { return _rootNodes; }
}
internal DataSet getData()
{
DataTable dt = dbm.Menu(dbm, "Menu");
DataSet ds = new DataSet();
ds.Tables.Add(dt);
//add a relationship
ds.Relations.Add("rsParentChild", ds.Tables["Table1"].Columns["Id"], ds.Tables["Table1"].Columns["ParentId"], false);
return ds;
}
private void Tree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<T> e)
{
string menuitem = MenusItem.text;
System.Windows.MessageBox.Show("menuitem");
}
You need to use the properties from the event to access the TreeViewItem that was selected (or in your case, the DataRowView). I have slightly modified the example from the for SelectedItemChanged so that it will match your code. Notice how the event data contains the DataRowView that was selected. You need to use the event data to get the selected item and then you can do whatever you need to do with the item.
private void Tree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<Object> e)
{
var item = (DataRowView) e.NewValue;
System.Windows.MessageBox.Show(item["Id"].ToString());
}
You can create a bindable SelectedItem property using WPF Behaviors
public class perTreeViewHelper : Behavior<TreeView>
{
public object BoundSelectedItem
{
get => GetValue(BoundSelectedItemProperty);
set => SetValue(BoundSelectedItemProperty, value);
}
public static readonly DependencyProperty BoundSelectedItemProperty =
DependencyProperty.Register("BoundSelectedItem",
typeof(object),
typeof(perTreeViewHelper),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnBoundSelectedItemChanged));
private static void OnBoundSelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (args.NewValue is perTreeViewItemViewModelBase item)
item.IsSelected = true;
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
base.OnDetaching();
}
private void OnTreeViewSelectedItemChanged(object obj, RoutedPropertyChangedEventArgs<object> args)
{
BoundSelectedItem = args.NewValue;
}
}
More details and a wider general discussion TreeViews in WPF on my blog post.

How to prevent ScrollViewer firing ViewChanged event while it is updating?

I have two ScrollViewer and I need to sync position of those ScrollViewer when any of the ScrollViewer gets changes, but right now suppose when anyone scrollviewer2 is changed then on calling ChangeView event of ScrollViewer1 it's firing its ViewChangedEvent which is resetting ScrollViewer2 position back.
private void Scroll(ScrollViewer changedScrollViewer)
{
var group = ScrollViewers[changedScrollViewer];
VerticalScrollOffsets[group] = changedScrollViewer.VerticalOffset;
HorizontalScrollOffsets[group] = changedScrollViewer.HorizontalOffset;
foreach (var scrollViewer in ScrollViewers.Where(s => s.Value == group && s.Key != changedScrollViewer))
{
scrollViewer.Key.ViewChanged -= ScrollViewer_ViewChanged;
if (scrollViewer.Key.VerticalOffset != changedScrollViewer.VerticalOffset)
{
scrollViewer.Key.ChangeView(null, changedScrollViewer.VerticalOffset, null, true);
}
if (scrollViewer.Key.HorizontalOffset != changedScrollViewer.HorizontalOffset)
{
scrollViewer.Key.ChangeView(changedScrollViewer.HorizontalOffset, null, null, true);
}
//Commenting this line works. But I need to set ViewChange event back.
scrollViewer.Key.ViewChanged += ScrollViewer_ViewChanged;
}
}
#Nico's solution is much preferable. If you still need something with a flag, it 'll look like this:
bool is_programmatic_call = false;
private void ScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
if (is_programmatic_call)
{
is_programmatic_call = false;
return;
}
if(sender == ScrollViewer1)
{
ScrollViewer2.ViewChanged -= ScrollViewer_ViewChanged;
is_programmatic_call = true;
ScrollViewer2.ChangeView(ScrollViewer1.HorizontalOffset, ScrollViewer1.VerticalOffset, null, true);
ScrollViewer2.ViewChanged += ScrollViewer_ViewChanged;
}
else
{
ScrollViewer1.ViewChanged -= ScrollViewer_ViewChanged;
is_programmatic_call = true;
ScrollViewer1.ChangeView(ScrollViewer2.HorizontalOffset, ScrollViewer2.VerticalOffset, null, true);
ScrollViewer1.ViewChanged += ScrollViewer_ViewChanged;
}
}
both the ScrollViewer's ViewChanged event is handled by this ScrollViewer_ViewChanged
For sync two ScrollViewers, the better way is make a new Dependency Property, and bind it with the same value. It will notify the ScrollViewer to scroll automatically when the Dependency Property value changed. This solution will stop Circular Reference happening in the ViewChanged event.
I have implemented it for ListView in this code sample. You could refer segment code. But for ScrollViewer, you need to make xaml Behavior, because ScrollViewer is sealed class, it could not be inherited.
public class SyncBehavior : Behavior<ScrollViewer>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += OnAssociatedObjectLoaded;
AssociatedObject.LayoutUpdated += OnAssociatedObjectLayoutUpdated;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= OnAssociatedObjectLoaded;
AssociatedObject.LayoutUpdated -= OnAssociatedObjectLayoutUpdated;
}
private void OnAssociatedObjectLayoutUpdated(object sender, object o)
{
SyncPointOffSetY();
}
private void OnAssociatedObjectLoaded(object sender, RoutedEventArgs routedEventArgs)
{
SyncPointOffSetY();
AssociatedObject.Loaded -= OnAssociatedObjectLoaded;
}
private void SyncPointOffSetY()
{
if (AssociatedObject == null) return;
AssociatedObject.ViewChanged += AssociatedObject_ViewChanged;
}
private void AssociatedObject_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
var MyScrollViewer = sender as ScrollViewer;
this.SetValue(PointOffSetYProperty, MyScrollViewer.VerticalOffset);
}
public double PointOffSetY
{
get { return (double)GetValue(PointOffSetYProperty); }
set { SetValue(PointOffSetYProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PointOffSetYProperty =
DependencyProperty.Register("PointOffSetY", typeof(double), typeof(SyncBehavior), new PropertyMetadata(0.0, CallBack));
private static void CallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var current = d as SyncBehavior;
var temScrollViewer = current.AssociatedObject;
if (e.NewValue != e.OldValue & (double)e.NewValue != 0)
{
temScrollViewer.ScrollToVerticalOffset((double)e.NewValue);
}
}
}
Usage
<ScrollViewer >
<Interactivity:Interaction.Behaviors>
<local:SyncBehavior PointOffSetY="{Binding PointY,Mode=TwoWay}"/>
</Interactivity:Interaction.Behaviors>
<StackPanel >
<Rectangle Height="500" Fill="Red"/>
<Rectangle Height="500" Fill="Black"/>
<Rectangle Height="500" Fill="Yellow"/>
</StackPanel>
</ScrollViewer>
<ScrollViewer Grid.Column="1" >
<Interactivity:Interaction.Behaviors>
<local:SyncBehavior PointOffSetY="{Binding PointY,Mode=TwoWay}"/>
</Interactivity:Interaction.Behaviors>
<StackPanel >
<Rectangle Height="500" Fill="Red"/>
<Rectangle Height="500" Fill="Black"/>
<Rectangle Height="500" Fill="Yellow"/>
</StackPanel>
</ScrollViewer>
And I have also added the above code to the sample that you could refer easily.

TreeView SelectedItem Behavior - Two Way Binding does not work in One Direction

I have created a very simple example to show my problem. Maybe I just think in a wrong way.
I want to select an Item of my TreeView - and I would like to see it in the View (Blue background).
To realize the TwoWayBinding I use this Behavior: Data binding to SelectedItem in a WPF Treeview
public class BindableSelectedItemBehavior : Behavior<TreeView>
{
#region SelectedItem Property
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var item = e.NewValue as TreeViewItem;
if (item != null)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (this.AssociatedObject != null)
{
this.AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
}
private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
this.SelectedItem = e.NewValue;
}
}
But if I click on an Item it does not go into the 'if' of the OnSelectedItemChanged because e.newValue as TreeViewItem is null
My XAML is very simple:
<StackPanel>
<TreeView xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
ItemsSource="{Binding Items}">
<i:Interaction.Behaviors>
<local:BindableSelectedItemBehavior
SelectedItem="{Binding Item}" />
</i:Interaction.Behaviors>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Text}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<TextBox Text="{Binding Item.Text}"/>
</StackPanel>
Thank you guys!
Just for the sake of the convenience, here's the final solution combined of the OP and ghrod's answer:
namespace MyPoject.Behaviors
{
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
public class BindableSelectedItemBehavior : Behavior<TreeView>
{
#region SelectedItem Property
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
nameof(SelectedItem),
typeof(object),
typeof(BindableSelectedItemBehavior),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnSelectedItemChanged));
static void OnSelectedItemChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
var behavior = (BindableSelectedItemBehavior)sender;
var generator = behavior.AssociatedObject.ItemContainerGenerator;
if (generator.ContainerFromItem(e.NewValue) is TreeViewItem item)
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (this.AssociatedObject != null)
AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
void OnTreeViewSelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> e) =>
SelectedItem = e.NewValue;
}
}
SelectedItem property of TreeView does not return TreeViewItem in your case. It returns currently selected item from your bound Items collection. To get TreeViewItem from SelectedItem, you need to use ItemContainerGenerator here:
private static void OnSelectedItemChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
var behavior = (BindableSelectedItemBehavior)sender;
var generator = behavior.AssociatedObject.ItemContainerGenerator;
var item = generator.ContainerFromItem(e.NewValue) as TreeViewItem;
if (item != null)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
}
Your OnSelectedItemChanged will only pass an object of type TreeViewItem if your actual model objects are of type TreeViewItem, which isn't going to be the case 99% of the time. You will instead have to retrieve TreeViewItem from a model object, but it will not be available if the node is currently collapsed, which makes selecting collapsed nodes very non-trivial.
I've made an effort to explain this very thoroughly in my blog, including code samples here

How to get item details from an ItemCollection in a GridView

I've a GridView page with different elements (ItemExplorer). Each element has two TextBlocks (Name & Description) and they're binding to a Collection.
I want that to when I click in a single element, a new page should be opened with the item details (ItemViewer), like a name or description...
I'm new in C#, so I appreciate any help or support! :)
This is my current code:
ItemExplorer.xaml:
<GridView ItemsSource="{Binding ItemList}">
<GridView.ItemTemplate>
<DataTemplate>
<Border DoubleTapped="GoToItemViewer_DoubleTapped">
<StackPanel>
<TextBlock Name="ItemName" Text="{Binding ItemName}"/>
<TextBlock Name="ItemDescription" Text="{Binding ItemDescription}"/>
</StackPanel>
</Border>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
ItemExplorer.xaml.cs:
namespace Test001.Pages
{
public sealed partial class ItemExplorer : Page
{
public ItemCollection MyItemCollection;
public ItemExplorer()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
MyItemCollection = new ItemCollection();
this.DataContext = MyItemCollection;
}
// Go to ItemViewer
private void GoToItemViewer_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
if (this.Frame != null)
{
// SEND THE ITEM DETAILS AS PARAMTER TO ITEMVIEWER
this.Frame.Navigate(typeof(ItemViewer));
}
}
}
}
ItemCollection.cs
Collectionnamespace Test001.DataSource
{
public class CollectionFiles: BindableBase
{
private ItemCollection _ItemList = new ItemCollection();
public ItemCollection ItemList
{
get { return _ItemList; }
set { SetProperty(ref _ItemList, value); }
}
public CollectionFiles()
{
_ItemList.Add(new FileModel()
{
ItemName = "ItenName0",
ItemDescription = "Iten Description0",
});
_ItemList.Add(new FileModel()
{
ItemName = "ItenName1",
ItemDescription = "Iten Description1",
});
_ItemList.Add(new FileModel()
{
ItemName = "ItenName2",
ItemDescription = "Iten Description2",
});
_ItemList.Add(new FileModel()
{
ItemName = "ItenName3",
ItemDescription = "Iten Description3",
});
}
}
}
ItemViewer.xaml.cs
namespace mydox104.Pages
{
public sealed partial class DocumentViewer : mydox104.Common.LayoutAwarePage
{
public DocumentViewer()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// GET THE ITEM DETAILS
string file_name = e.Parameter as string;
if (!string.IsNullOrWhiteSpace(file_name))
{
pageTitle.Text = file_name;
}
else
{
pageTitle.Text = e.Parameter.ToString();
}
}
}
}
ItemViewer.xaml
<Grid Height="225">
<TextBlock x:Name="Item-Name" Text=""/>
<TextBlock x:Name="Item-Description" Text=""/>
</Grid>
Set in GridView:
<GridView IsItemClickEnabled="True" ItemClick="ItemClicked" ItemsSource="{Binding ItemList}">
in ItemExplorer.xaml.cs
private void ItemClicked(object sender, ItemClickEventArgs e)
{
var clickedItem = e.ClickedItem as ItemCollection;
if (clickedItem != null )
{
this.Frame.NavigateTo(typeof(ItemViewer), clickedItem);
}
}
in ItemViewer.xaml.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ItemCollection myElement = e.Parameter as ItemCollection;
...
}
Instead of
public ItemCollection MyItemCollection;
i'd just use
public ObservableCollection<FileModel> Items;
I hope it will help you somehow. However I encourage you to look about MVVM pattern. It is really useful in developing WPF/WinRT applications. (check also MVVM Light framework which gives you for instance Messanger class - which have ability to "send objects/messages" between classes).

Categories

Resources