Databinding with TreeView in WPF - c#

I am working with data binding and tree views and I am not able to get my TreeView to populate in my WPF. I think I am relatively close, just a small tweak somewhere, but I can't seem to find it.
Here's my Project class:
public class Project
{
public Project(string Name, bool isFolder, Project ParentFolder)
{
this.Name = Name;
this.isFolder = isFolder;
Children = new List<Project>();
if (ParentFolder == null)
{
Path = Name;
}
else
{
Path = ParentFolder.Path + " > " + Name;
}
}
public string Path { get; private set; }
public string Name { get; set; }
public bool isFolder { get; set; }
public List<Project> Children { get; set; }
public IEnumerable<Project> ChildFolders
{
get
{
return Children.Where(p => p.isFolder);
}
}
public object Icon
{
get
{
if (isFolder)
{
return 0; // return folder icon
}
else
{
return 1; // return project icon
}
}
}
public IEnumerable<Project> SearchRecursively(string SearchString)
{
return GetAllChildren.Where(p => p.Name.Contains(SearchString));
}
private List<Project> GetAllChildren
{
get
{
List<Project> allChildren = new List<Project>();
foreach(Project child in Children)
{
allChildren.AddRange(child.GetAllChildren);
}
return allChildren;
}
}
}
}
Here is my MaiWindow.xaml.cs class that I will be using to make test data:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.BuildData();
}
private void BuildData()
{
List<Project> parents = new List<Project>();
Project parentOne = new Project("Apple", true, null);
Project parentTwo = new Project("Samsung", true, null);
Project parentThree = new Project("Google", true, null);
parents.Add(parentOne); parents.Add(parentTwo); parents.Add(parentThree);
Project appleMacBook = new Project("Mac", false, parentOne);
Project appleIpad = new Project("iPad", false, parentOne);
Project appleiPhone = new Project("iPhone", false, parentOne);
Project samsungGalaxy = new Project("Galaxy", false, parentTwo);
Project samsungNote = new Project("Note", false, parentTwo);
Project googlePixel = new Project("Pixel", false, parentThree);
Project googleChromecast = new Project("Chromecast", false, parentThree);
parents[0].Children.Add(appleMacBook); parents[0].Children.Add(appleIpad); parents[0].Children.Add(appleiPhone);
parents[1].Children.Add(samsungGalaxy); parents[1].Children.Add(samsungNote);
parents[2].Children.Add(googlePixel); parents[2].Children.Add(googleChromecast);
}
}
}
And here is my XAML where I am trying to display the TreeView. Right now, it is just blank. I would appreciate any tips.
<TreeView x:Name="Hierarchy" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="0,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="265"
ItemsSource="{Binding parents}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding parents}" DataType="{x:Type self:Project}">
<TreeViewItem Header="{Binding Name}"></TreeViewItem>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
Edit:
Here's the Property class:
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
private string name { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildFolders}">
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding Icon}" Margin="5, 5, 5, 5"></Image>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" BorderThickness="0" FontSize="16" Margin="5"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
So, this doesn't seem to be firing the change event. I know this because Path is set as Name + ">". When I change the Name, Path is not reflecting the change. It only shows what my previous value for Name was, if that makes sense.
if (ParentFolder == null)
{
Path = Name;
}
else
{
Path = ParentFolder.Path + " > " + Name;
}
Edit:
public Project(string Name, bool isFolder, Project ParentFolder)
{
this.Name = Name;
this.isFolder = isFolder;
Children = new List<Project>();
this.ParentFolder = ParentFolder;
}
public string Path
{
get
{
return this.ParentFolder + " > " + this.Name;
}
set
{
this.Path = Path;
}
}
XAML:
<TextBox x:Name="FolderNameBox" Grid.Column="1" Background="White" Grid.Row="1" Grid.ColumnSpan="5"
Margin="0,0,287,654.333" VerticalContentAlignment="Center"
Padding="6" FontSize="16"
IsReadOnly="True"
Text="{Binding ElementName=Hierarchy, Path=SelectedItem.Path, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
<TextBox x:Name="SearchProjectsBox" Grid.Column="5" Background="White" Grid.Row="1" Text="Search Projects"
Margin="47.333,0,0,654.333" VerticalContentAlignment="Center" Foreground="LightGray" Padding="6" FontSize="16" HorizontalAlignment="Left" Width="268" GotFocus="TextBox_GotFocus" LostFocus="TextBox_LostFocus"/>
<TreeView x:Name="Hierarchy" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="0,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="226"
ItemsSource="{Binding Projects}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildFolders}">
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding Icon}" Margin="5, 5, 5, 5"></Image>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" BorderThickness="0" FontSize="16" Margin="5"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<Grid Grid.ColumnSpan="2" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="245,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="540">
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
<ListView Margin="0,0,10,0" Name="ProjectView" ItemsSource="{Binding Projects}" FontSize="16" Foreground="Black">
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource GridHeader}">
<GridViewColumn Header="Name" Width="200" DisplayMemberBinding="{Binding ElementName=Hierarchy, Path=SelectedItem.Name, UpdateSourceTrigger=PropertyChanged}"></GridViewColumn>
<GridViewColumn Header="Directory" Width="328" DisplayMemberBinding="{Binding ElementName=Hierarchy, Path=SelectedItem.Path, UpdateSourceTrigger=PropertyChanged}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</ScrollViewer>
</Grid>
</Grid>
The Path updates too but when it I see it it will display the path of the project rather than the fired change of name. It changes in real-time but doesn't save the String value..only registers that a change has happened.
Heres my Property Change too.
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

You have a few problems here.
ItemsSource="{Binding parents}"
Here's parents:
private void BuildData()
{
List<Project> parents = new List<Project>();
You're asking XAML to examine all the methods in the codebehind class, looking for local variables named parents. This isn't a reasonable request.
There are a few requirements if you want to bind to parents: It must be...
A public...
Property (not a field -- it needs a get block)...
Of whatever object is your TreeView's DataContext.
None of those are true.
Two more things -- not required, but a very good idea:
Make it ObservableCollection<T> rather than List<T>, so that it will notify the UI of added or removed items.
The class that owns it should be a viewmodel class, not your window/usercontrol. When we say "viewmodel", we mean it implements INotifyPropertyChanged and raises PropertyChanged when its property values change. Again, this is about keeping the UI informed of changes.
Keeping the UI informed is what bindings are all about: They listen for changes in the viewmodel and update the UI.
So you need a main viewmodel that looks like this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// C#6
/*
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
*/
protected virtual void OnPropertyChanged(string propName)
{
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propName));
}
}
}
public class MainViewModel : ViewModelBase
{
private ObservableCollection<Project> _projects;
public ObservableCollection<Project> Projects {
get { return _projects; }
set {
if (value != _projects) {
_projects = value;
OnPropertyChanged(nameof(Projects));
}
}
}
public void BuildData() {
Projects = new ObservableCollection<Project>();
// do the rest of the stuff
}
}
And you should rewrite your Project class as a ProjectViewModel derived from ViewModelBase, make it raise PropertyChanged in the same way, and use ObservableCollection<Project> for Children.
And in your main window...
public MainWindow()
{
InitializeComponent();
var vm = new MainViewModel();
vm.BuildData();
DataContext = vm;
}
Your XAML needs a little work, too.
Projects has a capitalized name now
For the item template, you are binding to the property of the child item which provides the tree view item's children. That's the Children property of your Project class.
A datatemplate tells XAML how to present the content of a control. The tree creates a TreeViewItem with a Project as its DataContext, and then uses your HierarchicalDataTemplate to turn that DataContext into some kind of visual content. You don't use the template to create a TreeViewItem; you use it to create the visual stuff in the TreeViewItem.
So here's the new XAML:
<TreeView
x:Name="Hierarchy"
ItemsSource="{Binding Projects}"
Grid.Column="4"
HorizontalAlignment="Left"
Height="631"
Margin="0,58,0,0"
Grid.Row="1"
VerticalAlignment="Top"
Width="265"
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Label Content="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
There's no reason to get in the habit of DataContext = this;. Once you start that, the next thing you know you'll be doing it in a UserControl and coming here asking why all your bindings to it in the parent XAML are broken. Dependency properties are a bigger hassle than INPC, and you end up with code that ought to be in a viewmodel mixed into your MainWindow code. If you use viewmodelsit's the easiest thing in the world to shuffle your UI around. Maybe you want the original content of your main window to be just one of three tab pages in the main window. Keeping code separated properly makes that kind of thing much simpler.

Related

Returning bound checkbox values using MVVM in a WPF form

I have an object that consists of a string and an array. The string populates a ComboBox and the array populates a ListView depending on the selected string value. Each line of the ListViewconsists of a TextBlock and a CheckBox.
On submit I want to be able to verify which items have been selected for further processing but there's a disconnect when using the MVVM approach. I currently have the DataContext of the submit Button binding to the ListView but only the first value is being returned upon submit (somewhere I need to save the selected values to a list I assume but I'm not sure where). I added an IsSelected property to the model which I think is the first step, but after that I've been grasping at straws.
Model
namespace DataBinding_WPF.Model
{
public class ExampleModel { }
public class Example : INotifyPropertyChanged
{
private string _name;
private string[] _ids;
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected != value)
{
_isSelected = value;
RaisePropertyChanged("IsSelected");
}
}
}
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
public string[] IDs
{
get => _ids;
set
{
if (_ids != value)
{
_ids = value;
RaisePropertyChanged("IDs");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(property));
}
}
}
}
ViewModel
namespace DataBinding_WPF.ViewModel
{
public class ExampleViewModel : INotifyPropertyChanged
{
public ObservableCollection<Example> Examples
{
get;
set;
}
// SelectedItem in the ComboBox
// SelectedItem.Ids will be ItemsSource for the ListBox
private Example _selectedItem;
public Example SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
RaisePropertyChanged(nameof(SelectedItem));
}
}
// SelectedId in ListView
private string _selectedId;
public string SelectedId
{
get => _selectedId;
set
{
_selectedId = value;
RaisePropertyChanged(nameof(SelectedId));
}
}
private string _selectedCheckBox;
public string IsSelected
{
get => _selectedCheckBox;
set
{
_selectedCheckBox = value;
RaisePropertyChanged(nameof(IsSelected));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(property));
}
}
public void LoadExample()
{
ObservableCollection<Example> examples = new ObservableCollection<Example>();
examples.Add(new Example { Name = "Mark", IDs = new string[] { "123", "456" }, IsSelected = false });
examples.Add(new Example { Name = "Sally", IDs = new string[] { "789", "101112" }, IsSelected = false });
Examples = examples;
}
/* BELOW IS A SNIPPET I ADDED FROM AN EXAMPLE I FOUND ONLINE BUT NOT SURE IF IT'S NEEDED */
private ObservableCollection<Example> _bindCheckBox;
public ObservableCollection<Example> BindingCheckBox
{
get => _bindCheckBox;
set
{
_bindCheckBox = value;
RaisePropertyChanged("BindingCheckBox");
}
}
}
}
View
<UserControl x:Class = "DataBinding_WPF.Views.StudentView"
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:local = "clr-namespace:DataBinding_WPF"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Name"/>
<ListView x:Name="myListView"
ItemsSource="{Binding SelectedItem.IDs}"
DataContext="{Binding DataContext, ElementName=submit_btn}"
SelectedItem="{Binding SelectedId}"
Height="200" Margin="10,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox
Name="myCheckBox"
IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text="{Binding}" FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button HorizontalAlignment="Left" Height="20" Width="100"
Click="Submit" x:Name="submit_btn">Submit</Button>
</StackPanel>
</Grid>
</UserControl>
View.cs
namespace DataBinding_WPF.Views
{
/// <summary>
/// Interaction logic for StudentView.xaml
/// </summary>
public partial class StudentView : UserControl
{
public StudentView()
{
InitializeComponent();
}
private void Submit(object sender, EventArgs e)
{
var selectedItems = ((Button)sender).DataContext;
// process each selected item
// foreach (var selected in ....) { }
}
}
}
The ListView control already exposes a selected items collection as property SelectedItems.
private void Submit(object sender, RoutedEventArgs e)
{
var selectedIds = myListView.SelectedItems.Cast<string>().ToList();
// ...do something with the items.
}
However, I doubt that you want to do this in the code-behind, but rather in the view model. For this purpose, WPF offers the concept of commands.
MVVM - Commands, RelayCommands and EventToCommand
What you need is a relay command or delegate command (the name varies across frameworks). It encapsulates a method that should be executed for e.g. a button click and a method to determine whether the command can be executed as an object that can be bound in the view. Unfortunately, WPF does not provide an implementation out-of-the-box, so you either have to copy an implementation like here or use an MVVM framework that already provides one, e.g. Microsoft MVVM Tookit.
You would expose a property Submit of type ICommand in your ExampleViewModel and initialize it in the constructor with an instance of RelayCommand<T> that delegates to a method to execute.
public class ExampleViewModel : INotifyPropertyChanged
{
public ExampleViewModel()
{
Submit = new RelayCommand<IList>(ExecuteSubmit);
}
public RelayCommand<IList> Submit { get; }
// ...other code.
private void ExecuteSubmit(IList selectedItems)
{
// ...do something with the items.
var selectedIds = selectedItems.Cast<string>().ToList();
return;
}
}
In your view, you would remove the Click event handler and bind the Submit property to the Command property of the Button. You can also bind the SelectedItems property of the ListView to the CommandParameter property, so the selected items are passed to the command on execution.
<Button HorizontalAlignment="Left"
Height="20"
Width="100"
x:Name="submit_btn"
Command="{Binding Submit}"
CommandParameter="{Binding SelectedItems, ElementName=myListView}">Submit</Button>
Additionally, a few remarks about your XAML.
Names of controls in XAML should be Pascal-Case, starting with a capital letter.
You should remove the DataContext binding from ListView completely, as it automatically receives the same data context as the Button anyway.
DataContext="{Binding DataContext, ElementName=submit_btn}"
You can save yourself from exposing and binding the SelectedItem property in your ExampleViewModel, by using Master/Detail pattern for hierarchical data.
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
IsSynchronizedWithCurrentItem="True"
DisplayMemberPath="Name"/>
<ListView ItemsSource="{Binding Examples/IDs}"
SelectedItem="{Binding SelectedId}"
Height="200" Margin="10,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox Name="myCheckBox"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text="{Binding}"
FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button HorizontalAlignment="Left"
Height="20"
Width="100"
Command="{Binding Submit}"
CommandParameter="{Binding SelectedItems, ElementName=myListView}">Submit</Button>
</StackPanel>
</Grid>
If the view's data context is bound to the view then remove the DataContext from the ListView.
You could remove the item template and instead use a GridView like:
<ListView.View>
<GridView >
<GridViewColumn Header="Selected" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected}" Content="{Binding Name}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
Since the ItemSource is an Observable collection, there are several options to monitor changes in the checkboxes:
Add an event handler to the item changed event of the collection and then you can add the Name or the collection index to a local collection. e.g Examples[e.CollectionIndex].Name
Alternatively iterate over the observable collection and select those Examples where Selected = "true"

Pass ObservableCollection<> type as dependency property

I am trying to create a multi-select Combobox Custom control, This custom control should expose a dependency property called DropDownDataSource through which the user of the control can decide what day should bound to ComboBox. My code looks like this:
MainPage.Xaml
<Grid>
<local:CustomComboBox x:Name="customcb" DropDownDataSource="{x:Bind DropDownDataSource, Mode=OneWay}" Loaded="CustomControl_Loaded"> </local:CustomComboBox>
</Grid>
MainPage.Xaml.cs
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
private ObservableCollection<Item> _dropDownDataSource;
public ObservableCollection<Item> DropDownDataSource
{
get => _dropDownDataSource;
set
{
_dropDownDataSource = value;
OnPropertyChanged();
}
}
public MainPage()
{
this.InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private void CustomControl_Loaded(object sender, RoutedEventArgs e)
{
var Items = new ObservableCollection<Item>(Enumerable.Range(1, 10)
.Select(x => new Item
{
Text = string.Format("Item {0}", x),
IsChecked = x == 40 ? true : false
}));
DropDownDataSource = Items;
}
}
Models
public class Item : BindableBase
{
public string Text { get; set; }
bool _IsChecked = default;
public bool IsChecked { get { return _IsChecked; } set { SetProperty(ref _IsChecked, value); } }
}
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T storage, T value,
[System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (!object.Equals(storage, value))
{
storage = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
protected void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
CustomUserControl XAML
<Grid x:Name="GrdMainContainer">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBox Width="200" FontSize="24" Text="{Binding Header, Mode=TwoWay}"
IsReadOnly="True" TextWrapping="Wrap" MaxHeight="200" />
<ScrollViewer VerticalScrollBarVisibility="Auto" MaxHeight="200" Width="200" Background="White">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}"
FontSize="24"
Foreground="Black"
IsChecked="{Binding IsChecked, Mode=TwoWay}"
IsThreeState="False" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</StackPanel>
</Grid>
CustomUserControl Cs file
public sealed partial class CustomComboBox : UserControl
{
public CustomComboBox()
{
this.InitializeComponent();
}
public ObservableCollection<Item> DropDownDataSource
{
get { return (ObservableCollection<Item>)GetValue(DropDownDataSourceProperty); }
set { SetValue(DropDownDataSourceProperty, value); }
}
public static readonly DependencyProperty DropDownDataSourceProperty =
DependencyProperty.Register("DropDownDataSource", typeof(ObservableCollection<Item>), typeof(CustomComboBox), new PropertyMetadata("", HasDropDownItemUpdated));
private static void HasDropDownItemUpdated(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomComboBox ucrcntrl)
{
var grd = UIElementExtensions.FindControl<Grid>(ucrcntrl, "GrdMainContainer");
grd.DataContext = ucrcntrl.DropDownDataSource as ObservableCollection<Item>;
}
}
}
All looks good to me, but for some reason, Dropdown is coming empty. Instead of the dependency property, If I assign a view model directly to the Control it works fine. But in my condition, it is mandatory that I have properties like DataSource,SelectedIndex, etc on the user control for the end-user to use. Can anyone point out what is going wrong here?
Here, I have attached a copy of my complete code.
I downloaded your sample code, the problem should be in the binding.
<ItemsControl ItemsSource="{Binding Items}">
This way of writing is not recommended. In the ObservableCollection, Items is a protected property and is not suitable as a binding property.
You can try to bind dependency property directly in ItemsControl:
<ItemsControl ItemsSource="{x:Bind DropDownDataSource,Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="local:Item">
<CheckBox IsChecked="{x:Bind IsChecked, Mode=TwoWay}"
IsThreeState="False" >
<TextBlock Text="{x:Bind Text}" Foreground="Black" FontSize="24"/>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In addition, you may have noticed that I modified the style of CheckBox and rewritten the content to TextBlock, because in the default style of CheckBox, Foreground is not bound to the internal ContentPresenter.
Thanks.

How to update TextBlock text inside of ListBox item

So I have a simple UDP chat app from a WinForm project, which I wanted to look a little bit better, so I am re-making it in WPF. As I realized I can easily put 2 or more TextBlocks inside of a ListItem, I wanted to display the last message of each chat, like so:
But I have no Idea on how to edit those TextBlocks :( I literary just started with WPF, so I bet I just made a duplicate, but because of that, I don't even know how to search for this issue.
Here is the custom ListBox:
<ListBox x:Name="myList" HorizontalAlignment="Left" Width="264" ScrollViewer.VerticalScrollBarVisibility="Hidden" ScrollViewer.HorizontalScrollBarVisibility="Disabled" BorderThickness="0,1,1,0" MouseLeftButtonUp="myList_MouseLeftButtonUp" Margin="0,25,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="LightGray" BorderThickness="0,0,0,1" Width="250">
<DockPanel Margin="0,7">
<Ellipse Name="ellipse" Margin="5" DockPanel.Dock="Left" Style="{DynamicResource elstyle}">
</Ellipse>
<TextBlock Text="{Binding Name}" DockPanel.Dock="Top" Margin="0,0,0,7" FontWeight="Bold" MaxWidth="250"></TextBlock>
<TextBlock Text="{Binding ID}" DockPanel.Dock="Top" Visibility="Hidden" FontSize="1.333"></TextBlock>
<TextBlock x:Name="last_message" Text="{Binding LastMessage}" DockPanel.Dock="Bottom" MaxWidth="250"></TextBlock>
</DockPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is simplified model to show the principal but if you would create view model class that implement INotifyPropertyChanged interface to hold your item data
public class MyItem : INotifyPropertyChanged
{
private string _name;
private string _id;
private string _lastMessage;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public string ID
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("ID");
}
}
public string LastMessage
{
get { return _lastMessage; }
set
{
_lastMessage = value;
OnPropertyChanged("LastMessage");
}
}
}
and then in your window
public partial class MainWindow : Window
{
private readonly ObservableCollection<MyItem> _myItems = new ObservableCollection<MyItem>();
public MainWindow()
{
InitializeComponent();
myList.ItemsSource = _myItems;
_myItems.Add(new MyItem { Name = "name", ID = "id", LastMessage = "last message" });
_myItems[0].LastMessage = "new message";
}
}
and then you don't operate on myList control anymore but on _myItems list and its items. If you add/remove item in the collection it will add/remove item in the UI, if you change property of an item it will update bound property in the UI

How to bind a list in store apps

I have problem with binding list in store apps.
public class Category
{
public Category(int id, string name)
{
this.ID = id;
this.Name = name;
}
public int ID { get; set; }
public string Name { get; set; }
}
I created ColllecionViewSource and GridView
<CollectionViewSource x:Name="CategoriesViewSource" IsSourceGrouped="True"/>
<GridView ItemsSource="{Binding Source={StaticResource CategoriesViewSource}}" >
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"></TextBlock>
<TextBlock Text="{Binding ID}"></TextBlock>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
In constructor of my page i add list of Category to CollectionViewSource
public HubPage()
{
this.InitializeComponent();
this.navigationHelper = new NavigationHelper(this);
this.navigationHelper.LoadState += navigationHelper_LoadState;
List<Category> test = new List<Category>();
test.Add(new Category(1, "two"));
CategoriesViewSource.Source = test;
}
But it doesn't work... What i do wrong?
I think that the main problem is the use of x:Name attribute instead of x:Key that is supposed to be used - What's the difference between x:Key and x:Name in WPF? (it is for wpf but xaml and controls are nearly the same for win store apps)
<CollectionViewSource x:Key="CategoriesViewSource" IsSourceGrouped="True"/>
EDIT
Well, actually I am bit confused, because resource by key and other docs pertained for WPF XAML say that x:Key should be used for resources, while this WinRT XAML example on MSDN shows the equivalent use of x:Name
But MSDN also says:
In general, x:Name should not be applied in situations that also use
x:Key. XAML implementations by specific existing frameworks have
introduced substitution concepts between x:Key and x:Name, but that is
not a recommended practice.
So, now I am not sure that it is source of problems.
EDIT:
Try to
HubPage: Page, INotifyPropertyChanged
{
private void OnPropertyChanged(string propName)
{ if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName)); }
...
public HubPage()
{
this.InitializeComponent();
this.navigationHelper = new NavigationHelper(this);
this.navigationHelper.LoadState += navigationHelper_LoadState;
List<Category> test = new List<Category>();
test.Add(new Category(1, "two"));
this.Categories = new ObservableCollection<Category>(test);
}
private ObservableCollection<Category> _Categories;
ObservableCollection<Category> Categories
{
get {return this._Categories;}
private set
{
this._Categories = value;
this.OnPropertyChanged("Categories");
}
}
}
<Page DataContext="{Binding RelativeSource={RelativeSource Self}}"
...
<GridView ItemsSource="{Binding Categories}" >
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"></TextBlock>
<TextBlock Text="{Binding ID}"></TextBlock>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
Page is your main element in XAML.

Binding class to ListView data template

I am trying to bind a ListView with a Model List. The xaml is as -
<ListView Name="lvProductBinding" HorizontalAlignment="Left" Height="434" Margin="10,144,0,0" VerticalAlignment="Top" Width="909">
<ListView.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding ProductNo}" HorizontalAlignment="Left" Margin="967,153,-912,0" VerticalAlignment="Top" Width="895" Height="224" IsExpanded="False">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Height="195" VerticalAlignment="Top" Width="895" Margin="0,0,-2,0">
<Label Content="{Binding ProductDescription}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="181" Height="27" />
<Label Content="{Binding VendorName}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="181" Height="27" />
</StackPanel>
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In mu xaml.cs I am doing this inside the constructor -
List<ProductDetailsModel> products;
products = new List<ProductDetailsModel>();
ProductDetailsModel objProductDetailsModel = new ProductDetailsModel();
objProductDetailsModel.VendorProductInventory = new VendorProductInventory() { ProductNo = "XS-3487", ProductDescription = "Perfume", VendorName = "JohnDoe" };
products.Add(objProductDetailsModel);
objProductDetailsModel = new ProductDetailsModel();
objProductDetailsModel.VendorProductInventory = new VendorProductInventory() { ProductNo = "TT-23487", ProductDescription = "Shoes", VendorName = "Richard Gere" };
products.Add(objProductDetailsModel);
objProductDetailsModel = new ProductDetailsModel();
objProductDetailsModel.VendorProductInventory = new VendorProductInventory() { ProductNo = "VFG-33487", ProductDescription = "Socks", VendorName = "Tom Cruise" };
products.Add(objProductDetailsModel);
lvProductBinding.ItemsSource = products;
where the ProductDetailsModel class is defined as -
public class ProductDetailsModel : INotifyPropertyChanged
{
public ProductDetailsModel()
{
}
private VendorProductInventory _vendorProductInventory;
public VendorProductInventory VendorProductInventory
{
get
{
return _vendorProductInventory;
}
set
{
if (_vendorProductInventory != value)
{
_vendorProductInventory = value;
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, e);
}
}
}
Can someone please advise as to what I am doing wrong here.
Eagerly waiting for a reply.
Thanks,
Saket
There are several things wrong with the code you posted.
First, as Sheridan noted, your implementation of INotifyPropertyChanged is wrong, automatic updates to the data in the ListView won't work until it is correct.
Second, all of the bindings listed in your XAML file don't have matching public properties in the ProductDetailsModel class. More than likely your ListView is being populated and all the bindings are failing (you should see exceptions for this in the VS output window at runtime). Your bindings, as written, should look like:
{Binding Path=VendorProductInventory.ProductNo}

Categories

Resources