TreeView auto-selecting parent after user update selected child WPF command - c#

I have a 2 level WPF treeview. When I click on a child item, the correct selectedCommand is triggered and all works well.
But when I click on my details view and update the field of this selected item, it unselect my childItem and fired the parent command because the parent is selected, but I need to just keep my childItem selected.
I have found some topics about the same problem, but I use command for my binding and not just code behind so I don't know how to make this solution work for me.
Dispacher.BeginInvoke method : > Parent TreeView Item ghost selected event!
e.Handled : > WPF TreeviewItem parent selected event fired after selecting a child?
And a topic that said it's a focus problem : TreeView auto-selecting parent after user selects child
Trigger of my command :
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction
Command="{Binding ItemSelectedCommand}"
CommandParameter="{Binding SelectedItem, ElementName=TreeView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
My Treeview :
<TreeView x:Name="TreeView"
ItemsSource="{Binding Modules}">
<HierarchicalDataTemplate DataType="{x:Type module:ParentViewModel}" ItemsSource="{Binding ChildItems}">
<TextBlock Text="Parent"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type module:ChildViewModel}">
<TextBlock Text="{Binding Path=childName}"/>
</HierarchicalDataTemplate>
</TreeView>
And here is my command which is in my ViewModel file
public ICommand ItemSelectedCommand
{
get
{
return _itemSelectedCommand ?? (_itemSelectedCommand = new CommandHandler(param => SelectedCommand(param)));
}
}
public void SelectedCommand(object selectedItem)
{
//code to activate my details view with prism
if(selectedItem.GetType().Name == "ParentType")
{
ActivateParentView();
}
else
{
ActivateDetailsView(.....); //activate child view
}
}
So, I trigger the same command when selected an item in my treeview but when I select the child item, the parent event is also triggered so my command is triggered 2 times and activate my parent view and don't stay in my child view. How can I stop propagating the event if I have already pass through child command ? How can I make it working with my command and not in code behind ?
EDIT
Here is my function ActivateDetailsView() which is called by my SelectedCommand
I have 13 modules and I have one detailsView in each module, so when I click on my selected item, I will search using reflexivity for the view I need to activate and I use PRISM library to activate it.
private void ActivateDetailsView(string nameTypeItem,IRegion tabConfigurationRegion, ModuleItemBaseViewModel selectedItem)
{
try
{
string viewName = "ModuleItem" + nameTypeItem + "DetailsView";
string moduleName = "Module" + nameTypeItem;
string fullViewName = moduleName + ".Views." + viewName + ", " + moduleName;
var typeOfCurrentView = Type.GetType(fullViewName);
//var view = Activator.CreateInstance(typeOfCurrentView);
var view = tabConfigurationRegion.GetView(viewName);
if (view == null)
{
view = _container.Resolve(typeOfCurrentView);
// Add the view to the main region. This automatically activates the view too.
tabConfigurationRegion.Add(view, viewName);
}
// The view has already been added to the region so just activate it.
tabConfigurationRegion.Activate(view);
string viewModelName = "ModuleItem" + nameTypeItem + "DetailsViewModel";
string fullViewModelName = moduleName + ".ViewModels." + viewModelName + ", " + moduleName;
var typeOfCurrentViewModel = Type.GetType(fullViewModelName);
//equivalent to ModuleItemSiemensDetailsViewModel viewModelM = view.DataContext as ModuleItemSiemensDetailsViewModel;
var propertyInfoDataContext = view.GetType().GetProperty("DataContext");
var viewModelModuleItem = propertyInfoDataContext.GetValue(view, null);
if (viewModelModuleItem != null)
{
PropertyInfo prop = typeOfCurrentViewModel.GetProperty("CurrentModuleItem");
//equivalent to viewModelModuleItem.CurrentModuleItem = selectedItem as ModuleItemBaseViewModel;
prop.SetValue(viewModelModuleItem, selectedItem, null);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
Debug.WriteLine(ex.StackTrace);
}
}
To reproduce the problem : Start a new WPF project, Add one main project in your solution with a view with a treeview and a treeviewviewmodel with the list of ParentViewModel which will be display on your treeview. Define an other project with 2 viewmodels : one class ParentVM with an ObservableCollection and a childViewModel, let one or two property like name, and bind it to the treeview. In the second project, define a detailsView to see properties of your ChildViewModel. On your main project, in the treeviewviewmodel add a SelectedCommandFunction which use prism to activate your detailView. (You need to have a DetailsView when you click on the Parent and an other one when you click on the ChildView.

Every TreeViewItem is in the visual tree of its parent, and the event will bubble down, so if you clicked the tree leaf, and it has two parents, then the event will be called 3 times.
Ways to prevent this:
Bind the IsSelected of the TreeItem directly to IsSelected of your VM, and do your thing OnPropertyChanged of IsSelected property of your VM.
In a roundabout way: in codebehind, when you handle the event, check if the OriginalSource is the same as the sender (similar to here)
Implement your own EventTrigger that marks events as Handled

Related

menuItem.InputGestureText not showing shortcut text

In my application i am creating menu items in code. This is the code for creating menu item
public MenuItem getMenuItem(string toolTip, string menuTitle, Uri menuIconUri, int? tagOnlyForHeaders, string shortCutKeyText ="")
{
MenuItem menuItem = new MenuItem
{
ToolTip = toolTip,
Header = menuTitle
};
if (menuIconUri != null)
{
menuItem.Icon = new Image
{
Source = new BitmapImage(menuIconUri)
};
}
if (tagOnlyForHeaders != null) {
menuItem.Tag = tagOnlyForHeaders;
}
if (shortCutKeyText != "") {
menuItem.InputGestureText = shortCutKeyText;
}
return menuItem;
}
but if I pass value for input Gesture like ctrl+n it is not displaying the shortcut text in the menu item while the application is running. what is wrong in this code. can anyone tell a solution for this.
This is how i add menu items
//Top level Op menu
var opMenuItem = utils.getMenuItem("OP", MenuName, null, 0);
//op registration
var RegistrationMenuItem = utils.getMenuItem("New Registration", "New Registration",
new Uri(baseIconUri + "newRegistration.png"), null,"ctrl+n ");
opMenuItem.Items.Add(opRegistrationMenuItem);
return opMenuItem;
and this is added to the main menu
mainMenu.Items.Add(menuItem); // in this case the "opMenuItem"
The InputGestureText only works when the MenuItem is not a direct child of the Menu(i.e Not directly inside the Items collection). If you want to see the InputGestureText, you need to add MenuItem to the Items collection of another MenuItem. To illustrate what I said, here is an example in XAML.
<Menu>
<MenuItem Header="File" InputGestureText="Ctrl+Z">
<MenuItem InputGestureText="Ctrl+C" Header="Open"></MenuItem>
</MenuItem>
</Menu>
The InputGestureText of the MenuItem (Header = "File) is not visible but that of MenuItem (Header="Open") is visible.
That was a problem with the library(Material design library) i was using to style user interface . I've reported the issue and it is fixed.

Export WPF Treeview into Excel

I have a TreeView in my WPF application.Now as per my requirement i want to export into Excel format but i am not getting how to start with it.
Here is my TreeView Creation C# Code..
private void TreeView_Loaded(object sender, RoutedEventArgs e)
{
// ... Create a TreeViewItem.
item.Header = "Computer";
item.ItemsSource = new string[] { "Monitor", "CPU", "Mouse" };
// ... Create a second TreeViewItem.
TreeViewItem item2 = new TreeViewItem();
item2.Header = "Outfit";
item2.ItemsSource = new string[] { "Pants", "Shirt", "Hat", "Socks" };
// ... Get TreeView reference and add both items.
var tree = sender as TreeView;
tree.Items.Add(item);
tree.Items.Add(item2);
}
private void TreeView_SelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> e)
{
var tree = sender as TreeView;
// ... Determine type of SelectedItem.
if (tree.SelectedItem is TreeViewItem)
{
// ... Handle a TreeViewItem.
var item = tree.SelectedItem as TreeViewItem;
this.Title = "Selected header: " + item.Header.ToString();
}
else if (tree.SelectedItem is string)
{
// ... Handle a string.
this.Title = "Selected: " + tree.SelectedItem.ToString();
}
}
Please help me to get it exported into Excel.
Thanks in advance..
As mentioned in your other question, in WPF, we data bind properties to UI controls, so in your case you should have a collection property data bound to the TreeView.ItemsSource property. If you did, then all you'd need to do is to iterate through that collection and populate your Excel data from it directly.
So your first error is trying to populate your TreeView from the TreeView_Loaded event handler. I already told you what you needed to do, so I'm a bit surprised that you completely ignored my advise and continued along the incorrect path. Define a collection property to data bind to the TreeView.ItemsSource property... you also need to declare a custom data type class with the required properties, eg. one collection property to data bind to the child node's ItemsSource property:
public ObservableCollection<YourClass> Items
{
get { return items; }
set { items = value; NotifyPropertyChanged("Items"); }
}
Then we data bind this to the TreeView.ItemsSource property:
<TreeView ItemsSource="{Binding Items}" />
Finally, we need to declare a HierarchicalDataTemplate to define what each item should look like:
<TreeView ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ItemsPropertyInYourClass}">
<TextBlock Text="{Binding NameOfPropertyInYourClass}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
So that's how you display items in a TreeView properly... please see the TreeView and HierarchicalDataTemplate, Step-by-Step page on MSDN for further help with this.
Now all you need to do is to iterate through your collection to populate your Excel data. Rather than going through all of that code now, I'd rather direct you to read an online tutorial, so please take a look at the Export data to Excel using C# on matijabozicevic.com.
You still need to read the Data Binding Overviewā€ˇ page on MSDN for further help with data binding.

ContentPresenter not updating display correctly

In my program's main window I have a TreeView and a ContentPresenter. The display of the ContentPresenter is determined by what node is selected in the TreeView.
The name of one of my nodes is allowed to be changed by the user via contentMenu. All the user has to do is right click the node and select the new name out of the choices. The ContentPresenter is supposed to have a null display until the user chooses a name for the node.
The problem occurs when a new name is selected from the contentMenu. The ContentPresenter's display changes, like it should, but only after the user selects a different node (changing the display), and then re-selects the original node.
How do I make it so that the display on the ContentPresenter changes right when the TreeView node's name is changed?
TreeViewViewModel:
public class TreeViewViewModel : PropertyChangedBase
{
public TreeViewViewModel()
{
Node = new Node() { NodeName = "Blank", NodeDataModel = new NodeModel(),
Commands = { new Command(nodeType_name1), new Command(nodeType_name2) } };
}
//These functions call to the NodeName property in the TreeView's Data Model
private void nodeType_name1()
{
Node.NodeName = "Name1";
}
private void nodeType_name2()
{
Node.NodeName = "Name2";
}
}
XAML for MainWindow:
<!-- Tree view items & Functions -->
<TreeView Name="Tree_One" ItemsSource="{Binding DataTree.Data}" ... >
<TreeView.Resources>
<SolidColorBrush Color="LightSkyBlue" x:Key="{x:Static SystemColors.HighlightBrushKey}" />
</TreeView.Resources>
</TreeView>
<!--- Left Widget -->
<ContentPresenter Content="{Binding LeftWidget}" />
MainWindowViewModel:
public class MainWindowViewModel : PropertyChangedBase
{
private TreeViewViewModel _dataTree;
public MainWindowViewModel()
{
_dataTree = new TreeViewViewModel();
}
public TreeViewViewModel DataTree { ... }
//This function is in charge of changing the display of the ContentPresenter
// I think that my problem can probably be solved by doing something here
public void ChangeViews()
{
if (_dataTree.SelectedItem is Node)
{
var _node = _dataTree.SelectedItem as Node;
var nodeViewModel = new NodeViewModel(_node.NodeDataModel);
if (_node.NodeName== "Unknown")
LeftWidget = null; //This is the Content Presenter **
if (_node.NodeName == "Name1")
{
LeftWidget = nodeViewModel;
}
if (_node.NodeName == "Name2") {...}
}
}
}
Duh, thats a alot of code and its pretty difficult to understand what you up to since you seem to have controls in your ViewModel.
Or at least it looks to me that you have them in ViewModel. That is not very MVVM-alike my friend. :)
"The problem occurs when a new name is selected from the contentMenu. The ContentPresenter's display changes, like it should, but only after the user selects a different node (changing the display), and then re-selects the original node."
The property changed is not being fired because the new selected value is equal to the old one.
Pretty obvious, right?... no property was actually changed
But why do you want the ContentPresenter to update itself with the value that it already has?
You said when you select a node the ContentPresenter displays it properly and when you re-select the same the ContentPresenter is not doing anything.
Its not doing anything because it think it doesnt need to. Which is true.
So the question is why would you make ContentPresenter force to refresh on each value no matter if old value is the same as new one?
Though if you want to hack/trick a little bit, you can always set ContentPresenter's Content to null before you assign another value. :)
However, post us more code and we will be able to provide you a better solution to your issue.
I was able to fix this issue by calling ChangeViews(); in my MainWindowViewModel from my TreeViewViewModel. I did this by using a delegate property in the TVVM, and adding it to my MWVM. By doing this, the display is updated whenever ChangeViews(); is called.
This is the answer that I used.

Windows Store App: How to make ListView with expandable/enlargeable ListItems?

I have a Listview with items, in a C# Windows Store App (is that what you call these? I heard they're not called Metro Apps anymore).
Similar to the ExpandableListView in Android, I want to be able to tap on listitems (not the buttons) for that listitem to expand, tap on the expanded listitem for it to collapse, and if you tap on another listitem, the currently expanded listitem will collapse and the other will expand.
In my particular case I have a DataTemplate for both the expanded and non-expanded view of the listitems. I've seen that Android's ExpandableListView can expand the listitem with additional information (the Expander from WPF does something similar to that), instead of replacing it with a larger item, but is there a common solution for this in Windows Store Apps?
If not, what is the closest equivalent?
Like on the following drawing, I want to know if there is a component that can expand listitems in this way, or if not, which alternatives I have:
I ended up with a solution that works but doesn't look too fancy. It switches DataTemplate when you click items but there's no animation: it switches instantly.
Here's the important code parts:
XAML
<Page.Resources>
<DataTemplate x:Key="dtSmall">
<!--Component template for the un-expanded listitems-->
</DataTemplate>
<DataTemplate x:Key="dtEnlarged">
<!--Component template for the expanded listitems-->
</DataTemplate>
</Page.Resources>
<Grid>
<ListView x:Name="lvEnlargeable"
IsItemClickEnabled="True"
ItemTemplate="{StaticResource dtSmall}"
ItemsSource="{Binding ...}"
SelectionChanged="LVEnlargeable_SelectionChanged"
ItemClick="LVEnlargeable_ItemClick"/>
</Grid>
XAML.CS
public sealed partial class MainPage : Page
{
private DataTemplate dtSmall;
private DataTemplate dtEnlarged;
public MainPage()
{
this.InitializeComponent();
dtSmall = (DataTemplate)Resources["dtSmall"];
dtEnlarged = (DataTemplate)Resources["dtEnlarged"];
}
// A selected item is treated as an expanded/enlarged item
private void LVEnlargeable_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
/* First we set all the items that has been deselected
to be collapsed, aka. using the dtSmall DataTemplate.
We expect 0 or 1 item to have been deselected
but handle all cases easily with a foreach loop.
*/
foreach (var item in e.RemovedItems)
{
// Set the DataTemplate of the deselected ListViewItems
((ListViewItem)(sender as ListView).ContainerFromItem(item)).ContentTemplate = dtSmall;
}
/* Then we set all the items that has been selected
to be expanded.
We should probably throw an Exception if more than 1 was found,
because it's unwanted behavior, but we'll ignore that for now.
*/
foreach (var item in e.AddedItems)
{
((ListViewItem)(sender as ListView).ContainerFromItem(e.AddedItems[0])).ContentTemplate = dtEnlarged;
}
}
/* We need click events because SelectionChanged-events
cannot detect clicks on an already selected item */
private void LVEnlargeable_ItemClick(object sender, ItemClickEventArgs e)
{
ListView lv = (sender as ListView);
/* Having set the IsItemClickEnabled property on the ListView to True
we have to handle selection events manually.
If nothing is selected when this click occurs, then select this item*/
if (lv.SelectedItem == null)
{
lv.SelectedItem = e.ClickedItem;
}
else
{
// Clicking on an expanded/selected/enlarged item will deselect it
if (lv.SelectedItem.Equals(e.ClickedItem))
{
lv.SelectedItem = null;
}
else
{ /* If it's not a selected item, then select it
(and let SelectionChanged unselect the already selected item) */
lv.SelectedItem = e.ClickedItem;
}
}
}
}
I haven't tested if this isolated code is enough, on its own, for this solution, but I hope it is, and this code at least contain the key points. It's late and I just wanted to post something for the curious-minded people. If this shows not to work for you, then please leave a comment about the issue and I'll make sure to add the missing parts.
I also messed with the ListViewItemStyleContainer's ListViewItemPresenter to have better selection effects etc. but I figure it's best to keep it short. If you find this interesting as well, then feel free to leave a comment for that too, and I'll try include it.

Bind Header of WPF ContextMenu

I had a problem with TreeView-Binding and ContextMenu here: Selected TreeViewItem is null
Now I'm having this problem: I have the ContextMenu
<TreeView.ContextMenu>
<ContextMenu x:Name="MyContext" ItemsSource="{Binding OCContext}" DisplayMemberPath="Text"/>
</TreeView.ContextMenu>
(The image shows how my ContextMenu looks like, don't mind about the tabItem...).
As you can see, it's just the ContetMenu, no MenuItem! If the user clicks on Close, I want to do something in my ViewModel (raise a Command?). I'd also like to know which button/Menu he clicked. The amount of Menus is dynamically, since it's ItemsSource is being binded.
This is my ViewModel:
private ObservableCollection<T_Antwort> _occontext;
public ObservableCollection<T_Antwort> OCContext
{
get
{
if (_occontext == null)
_occontext = new ObservableCollection<T_Antwort>();
return _occontext;
}
set
{
_occontext = value;
RaisePropertyChanged(() => OCContext);
}
}
So all I want to do is to bind the ContextMenu (The "items" Close and CloseOtherThankThis) to my ViewModel, so when the user clicks on one of them, I want to access them in my ViewModel. This means I don't want to bind them one by one, I want somehow to get an event (ContextMenuItemClicked (?)) being called and use this in my ViewModel.
Btw. using MenuItem under ContextMenu will create another "Menu folder", so it would be
" " -> Close
" " -> CloseOtherThankThis
And I don't want it to look like this.
Edit: I'm currently getting the item like this:
private void MyContext_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
System.Windows.Controls.Primitives.MenuBase s = sender as System.Windows.Controls.Primitives.MenuBase;
ItemCollection ic = s.Items;
T_Antwort SelectedItem = (T_Antwort)ic.CurrentItem;
}
Is there any possibility to get the selected item with binding?
Don't know if you have tried it, but there's a PlacementTarget for context menu, which gives you the object that contains the context menu.
In one project I had, I made something like this:
<MenuItem ... Visibility="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ContextMenu}},Path=PlacementTarget.SelectedItem

Categories

Resources