How to switch user controls based on treeview Selection Change. I have acheived this on ListBox but couldn't figure out how to do that with Wpf Treeview. Here is my XAML Code.
<Window x:Class="MainScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModelSettings="clr-namespace:ViewModel.Settings" >
<Window.Resources>
<DataTemplate DataType="{x:Type viewModelSettings:BasicSettingsViewModel}">
<viewSettings:BasicSettingsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModelSettings:AdvancedSettingsViewModel}">
<viewSettings:AdvancedSettingsView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<ListBox x:Name="ListBoxMenu"
Grid.Column="0" Margin="5,5,5,385"
ItemsSource="{Binding Settings}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Padding="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Border Grid.Column="1" Margin="5">
<ContentControl Content="{Binding ElementName=ListBoxMenu, Path=SelectedItem}"/>
</Border>
</Grid>
</Window>
I am using Data Template to define Various Viewmodels and binded views with them
To Make it completely MVVM, Here is my code behind
public partial class MainScreen : Window
{
public MainScreen()
{
InitializeComponent();
DataContext = new OptionsDialogViewModel();
}
}
// OptionsDialogViewModel Class
public class OptionsDialogViewModel : ViewModelBase
{
private readonly ObservableCollection<SettingsViewModelBase> _settings;
public ObservableCollection<SettingsViewModelBase> Settings
{
get { return this._settings; }
}
public OptionsDialogViewModel ()
{
_settings = new ObservableCollection<SettingsViewModelBase>();
_settings.Add(new BasicSettingsViewModel());
_settings.Add(new AdvancedSettingsViewModel());
}
}
// SettingsViewModelBase class
public abstract class SettingsViewModelBase : ViewModelBase
{
public abstract string Name { get; }
}
and now my ViewModel(s) are derived from this SettingsViewModelBase
public class AdvancedSettingsViewModel : SettingsViewModelBase
{
public override string Name
{
get { return "Advanced"; }
}
}
I have 2 questions now, Is this the right approach to do this task ?
How can I switch my list view to treeview
Unfortunately selecting items with a treeview is not as straight-forward as selecting with a ListBox.
Unlike simply binding to a ListBox with Content="{Binding ElementName=ListBoxMenu, Path=SelectedItem}", binding to a treeview item involves a bit of code-behind. Check out SO threads here and here for more discussion on the how's and why's.
Related
I have a user control like this
<UserControl
x:Class="App41.UserCntrl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d">
<Grid>
<ListView SelectionMode="Multiple" x:Name="lview" ItemsSource="{x:Bind ItemsSource, Mode=OneWay}" >
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{x:Bind PropertyNameToBind, Mode=OneWay}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
code behind
public sealed partial class UserCntrl : UserControl
{
public UserCntrl()
{
this.InitializeComponent();
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(UserControl),
new PropertyMetadata(null));
public IEnumerable ItemsSource
{
get => (IEnumerable)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public string PropertyNameToBind
{
get { return (string)GetValue(PropertyNameToBindProperty); }
set { SetValue(PropertyNameToBindProperty, value); }
}
public static readonly DependencyProperty PropertyNameToBindProperty =
DependencyProperty.Register("PropertyNameToBind", typeof(string), typeof(UserControl), new PropertyMetadata(""));
}
And I am calling this user control in my main page like this
<Page
x:Class="App41.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App41" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d">
<Grid>
<local:UserCntrl x:Name="lview" Loaded="EditTextControl_Loaded"></local:UserCntrl>
</Grid>
Code behind
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void EditTextControl_Loaded(object sender, RoutedEventArgs e)
{
ObservableCollection<OptionItem> io = new ObservableCollection<OptionItem>();
io.Add(new OptionItem { Name = "11111111111" });
lview.ItemsSource = io;
lview.PropertyNameToBind = "Name";
}
}
public class OptionItem
{
private string _Name = string.Empty;
public string Name
{
get { return _Name; }
set { _Name = value; }
}
}
All looks good to me, but ListView displays empty items instead of my content. I believe the issue is in this line
<TextBlock Text="{x:Bind PropertyNameToBind, Mode=OneWay}" />
Where I am trying to bing Name property inside the OptionItem Model. How can I solve this?
In UWP, each DataTemplate corresponds to an item in the ItemsSource, so the DataContext of the DataTemplate is limited to the item itself, specific to your project, is OptionItem.
You use {x:Bind PropertyNameToBind} is actually trying to bind OptionItem.PropertyNameToBind, but your OptionItem does not have this property, so nothing will be displayed.
Please determine whether you want to bind PropertyNameToBind or OptionItem.Name property, if it is OptionItem.Name, please use {x:Bind Name}.
If not, try this:
<UserControl
...
x:Name="Main">
<Grid>
<ListView SelectionMode="Multiple" x:Name="lview" ItemsSource="{x:Bind ItemsSource, Mode=OneWay}" >
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ElementName=Main,Path=PropertyNameToBind}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</UserControl>
Update
I think I understand what you mean, but you have some misunderstandings about binding.
What you bind is a property, and you pass a string value. This value cannot be used as a property name to reflect the properties of the binding class.
Combined with your needs, I recommend that you use the interface for dynamic processing:
interface
public interface ITest
{
string GetDisplayText();
}
class
public class OptionItem : ITest
{
public string Name { get; set; } = string.Empty;
public string GetDisplayText()
{
return Name;
}
}
control
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:ITest">
<TextBlock Text="{x:Bind GetDisplayText()}" />
</DataTemplate>
</ListView.ItemTemplate>
By creating an interface, a method for outputting displayed text is provided.
Any class that inherits this interface can override this method according to their needs.
Inside the control, you only need to call the GetDisplayText() method, regardless of which class is bound.
<Grid>
<ListView SelectionMode="Multiple" x:Name="lview" ItemsSource="{x:Bind ItemSource, Mode=OneWay}" >
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{x:Bind OptionItem.Name, Mode=OneWay}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
I have an ItemsControl who has an ItemsSource that is an ObservableCollection. The DataTemplate contains Label controls. My goal is to set the Content property of each of these Labels to the elements in the ObservableCollection but right now, the Content is entirely blank for each of the Label.
It is worth noting that this ItemsControl is nested within another, parent ItemsControl, but let me show:
<ItemsControl ItemsSource={Binding StudentCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
// This is the ItemsControl that is not working properly with the Labels
<ItemsControl ItemsSource="{Binding StudentActivitiesCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Sport, UpdatedSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.Template>
</ItemsControl>
This is my StudentsActivities class:
public class StudentActivities : INotifyPropertyChanged
private string sport;
public string Sport
{
get
{
return this.sport;
}
set
{
this.sport = value;
OnPropertyChanged("Sport");
}
}
}
}
And my working View Model:
private ObservableCollection<StudentActivities> studentActivitiesCollection;
public ObservableCollection<StudentActivities> StudentActivitiesCollection
{
get
{
if (studentActivitiesCollection == null)
studentActivitiesCollection = new ObservableCollection<StudentActivities>();
return studentActivitiesCollection;
}
}
This is the method I am using to populate my ObservableCollection in my ViewModel:
private void PopulateStudentActivitiesCollection(ObservableCollection<Student> Students)
{
foreach (Student s in Students)
{
StudentActivitiesCollection.Add(new StudentActivities () { Sport = StudentSport });
}
}
}
Change
<ItemsControl ItemsSource={StudentCollection}">
to
<ItemsControl ItemsSource={Binding StudentCollection}">
and
<Label Content="{Binding Sport, UpdatedSourceTrigger=PropertyChanged}"/>
to
<Label Content="{Binding Sport}"/>
The last change is not needed but not necessary either.
I've got a complex question I've been working on for a good week now trying to work correctly. I am using WPF .NET 4.5, the MVVM pattern, and Prism. I want to display a TreeView that binds to an ObservableCollection<IScript> LoadedScripts:
namespace Library.Data.Scripting
{
public interface IScript : INotifyPropertyChanged
{
...
IScriptDescription ScriptDescription { get; }
...
}
public interface IScriptDescription : INotifyPropertyChanged
{
...
string Name { get; }
IEnumerable<ISectionDescription> Sections { get; }
ScriptStatus Status { get; }
...
}
public interface ISectionDescription : INotifyPropertyChanged
{
...
string Name { get; }
ScriptStatus Status { get; }
...
}
}
I want the TreeView to display the list of scripts and each script to have children for the script's sections. For each node that is visible, I want to show the Name and Status. That is, for each IScript in LoadedScripts show a textbox of ScriptDescription.Name and ScriptDescription.Status. For each child ISectionDescription, a TextBox containing ISectionDescription.Name and ISectionDescription.Status.
This is all fairly straightforward, except for the fact that I'm binding to Interfaces. I found how I'm supposed to be able to overcome this, using a DataTemplateSelector, but my treeview isn't displaying the text correctly in my templates. Instead, I am getting the object.ToString() for the IScripts in my ObservableCollection<IScript> LoadedScripts with no children. I'm not getting any kind of errors or warnings in the Output window either.
I feel like I am really close but I can't get it to correctly display my data. I have verified I am indeed getting my data and if I put a basic DataTemplate for TreeView.ItemTemplate I am able to display the top-level IScript nodes. I'm not sure what I'm doing wrong, any input is much appreciated!
Here's my DataTemplateSelector:
namespace Library.App.GUI
{
public class ScriptDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is IScript)
{
return element.FindResource("ScriptTemplate") as HierarchicalDataTemplate;
}
else if (element != null && item != null && item is ISectionDescription)
{
return element.FindResource("SectionTemplate") as DataTemplate;
}
return null;
}
}
}
The ViewModel:
namespace Library.App.GUI
{
public class TestStatusViewModel
{
private IScriptManager ScriptManager;
public ObservableCollection<IScript> LoadedScripts
{
get
{
return ScriptManager.LoadedScripts;
}
}
public TestStatusViewModel()
{
//Locate the ScriptManager in the MEF container using Microsoft.Practices.ServiceLocation.ServiceLocator
ScriptManager = ServiceLocator.Current.GetInstance<IScriptManager>();
}
}
}
Here's my XAML:
<UserControl x:Class="Library.App.GUI.TestStatusView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:Library.App.GUI"
xmlns:script="clr-namespace:Library.Data.Scripting;assembly=MTFCommon"
xmlns:prism="clr-namespace:Microsoft.Practices.Prism;assembly=Microsoft.Practices.Prism"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="200">
<UserControl.Resources>
<local:ScriptDataTemplateSelector x:Key="ScriptTemplateSelector"/>
<HierarchicalDataTemplate x:Key="ScriptTemplate" ItemsSource="{Binding ScriptDescription.Sections}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" TextAlignment="Left" Text="{Binding ScriptDescription.Name}" />
<TextBlock Grid.Column="1" TextAlignment="Right" FontWeight="Bold" HorizontalAlignment="Stretch" Text="{Binding ScriptDescription.Status}" />
</Grid>
</HierarchicalDataTemplate>
<DataTemplate x:Key="SectionTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" TextAlignment="Left" Text="{Binding Name}" />
<TextBlock Grid.Column="1" TextAlignment="Right" FontWeight="Bold" HorizontalAlignment="Stretch" Text="{Binding Status}" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<ItemsControl HorizontalAlignment="Stretch">
<TreeView Name="treeView" ItemTemplateSelector="{StaticResource ScriptTemplateSelector}" ItemsSource="{Binding LoadedScripts}" />
</ItemsControl>
</UserControl>
P.S.: Everything in the Library.Data namespace I am not able to personally edit since it is an external library. I can put in a request to have it changed if that must happen, but prefer not.
P.P.S.: This is my first SO question, please let me know if I missed any important information. Thanks!
If your DataTemplateSelector not working, you might want to check to see if element.FindResource is returning anything. If it returns null (or your as returns null), then the ToString will be used to display the items in the tree.
I have a listbox inside a listbox, and both are binded to an observable collection. I have overloaded the SelectionChanged event for both. For the nested listbox, I have a few labels in its data template. I want to be able to get the content of those labels. It's just difficult because I cannot refer to any of them in the code behind, even with the x:name property defined. Anyone have any ideas?
<ListBox Grid.Row="5" x:Name="lb1" ItemsSource="{Binding}" DataContext="{Binding}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" SelectionChanged="lb1_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Label x:Name="txtEnclosure" Content="{Binding Path=EnclosureID}"/>
<......other labels bound to other properties...>
<ListBox x:Name="lbserver" ItemsSource="{Binding Path=Slist}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Label x:Name="txtSlot" Content="{Binding Path=Slot}" />
<Label x:Name="txtServer" Content="{Binding Path=HostnameID}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The parent listbox binds to an observable collection called Elist (an observable collection of Enclosures, a class I defined)
this.DataContext = Settings.Elist;
And the child listbox binds to an observable collection inside of the Enclosure class.
public class Enclosure
{
public ObservableCollection<Server> Slist { get; set; }
...contains other variables as well....
}
In the application, it lists enclosures, and each enclosure has a list of servers. The user can select an Enclosure, and I can get the Enclosure from Elist based on the SelectedIndex (I use ElementAt(SelectedIndex)). Things just get much more tricky when I try to get one of the Servers from the nested listbox. I want to be able to select one of the servers in the list and get the Server from the observable collection Slist. The problem is that when the user selects the server directly, I don't know which Enclosure from Elist the server is from, aaand I can't get the SelectedIndex because I can't refer to anything from the nested listbox in the code behind >.< A very frustrating problem indeed...does anyone have any ideas?
If I can get at the items in the nested listbox in code that would be helpful as well.
Below sample shows how to get selected parent and child when user selects a child, see OnSelectedModelChanged method.
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ListBox ItemsSource="{Binding .}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold"/>
<ListBox ItemsSource="{Binding Path=Models}" SelectionChanged="OnSelectedModelChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
Code behind:
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using System.Collections.Generic;
using System.Windows.Controls.Primitives;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Make> cars = new List<Make>();
cars.Add(new Make("Ford") { Models = new List<Model>() { new Model("F150"), new Model("Taurus"), new Model("Explorer") } });
cars.Add(new Make("Honda") { Models = new List<Model>() { new Model("Accord"), new Model("Pilot"), new Model("Element") } });
DataContext = cars;
}
private void OnSelectedModelChanged(object sender, SelectionChangedEventArgs e)
{
Selector modelSelector = sender as Selector;
Model selectedModel = modelSelector.SelectedItem as Model;
Make selectedMake = modelSelector.DataContext as Make;
}
}
public class Make
{
public Make(string name)
{
Name = name;
}
public string Name { get; private set; }
public IEnumerable<Model> Models { get; set; }
}
public class Model
{
public Model(string name)
{
Name = name;
}
public string Name { get; private set; }
}
}
I have the following code that should display some information about ContactLists in a ListBox but there seems to be a problem with the binding as nothing is displayed. What am I missing? Would appreciate any help. Thanks!
XAML
</Window>
<Window.Resources>
<DataTemplate x:Key="ContactsTemplate">
<WrapPanel>
<TextBlock TextWrapping="Wrap"
Text="{Binding ContactListName, Mode=Default}"/>
</WrapPanel>
</DataTemplate>
</Window.Resources>
<Grid x:Name="LayoutRoot"
Background="#FFCBD5E6">
<Grid.DataContext>
<local:MyViewModel/>
</Grid.DataContext>
<ListBox x:Name="contactsList"
SelectionMode="Extended"
Margin="7,8,0,35"
ItemsSource="{Binding ContactLists}"
ItemTemplate="{DynamicResource ContactsTemplate}"
HorizontalAlignment="Left"
Width="178"
SelectionChanged="contactsList_SelectionChanged"/>
</Grid>
</Window>
ViewModel
public class MyViewModel
{
public ObservableCollection<ContactListModel> ContactLists;
public MyViewModel()
{
var data = new ContactListDataAccess();
ContactLists = data.GetContacts();
}
}
Change ContactLists to be a property for the binding to work correctly:
public class MyViewModel
{
public ObservableCollection<ContactListModel> ContactLists{get;set;}
public MyViewModel()
{
var data = new ContactListDataAccess();
ContactLists = data.GetContacts();
}
}
See here for more info.