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.
Related
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"
I am working on a small wpf application, i am showing status using a textblock and for achieving this i have bind a property from a view model. I have mapped everything despite this UI is not showing any value. I am pasting code as below.
App.xaml.cs
public LogViewModel LogTextVm { get; set; }
ViewModelLocator.cs
App thisApp = (App)Application.Current;
public ViewModelLocator()
{
thisApp.LogTextVm = new LogViewModel();
}
LogViewModel.cs
public class LogViewModel : INotifyPropertyChanged
{
private string _logText { get; set; }
public string LogText
{
get => _logText;
set
{
if(_logText!=value)
{
_logText = value;
OnPropertyChnaged("LogText");
}
}
}
private void OnPropertyChnaged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
MainWindow.xaml
xmlns:vm="clr-namespace:MyApplication.ViewModels"
<Window.Resources>
<vm:ViewModelLocator x:Key="Locator"/>
</Window.Resources>
<TextBlock Grid.Column="0" Text="{Binding LogText, Mode=TwoWay}" DataContext="{Binding LogViewModel, Source={StaticResource Locator}}"
FontFamily="segoe UI" FontWeight="SemiBold" FontStyle="Italic" Foreground="White" />
MainWindow.xaml.cs
private App thisApp = (App)Application.Current;
public MainWindow()
{
InitializeComponent();
}
private async void CallGraphButton_Click(object sender, RoutedEventArgs e)
{
thisApp.LogTextVm.LogText = "Status : Loading data ...";
}
Can anyone help me, where i am doing mistake?
You have to assign MainWindow.DataContext with the same LogViewModel instance that is created by ViewModelLocator
DataContext="{Binding LogViewModel, Source={StaticResource Locator}}" can it create a new instance of the ViewModelLocator (and therefore thus new LogViewModel) ?
Please put the breakpoint at the thisApp.LogTextVm = new LogViewModel(); line and check is it executed twice?
UPD VS output shows binding error for your code
System.Windows.Data Error: 40 : BindingExpression path error: 'LogViewModel' property not found on 'object' ''ViewModelLocator' (HashCode=37320431)'. BindingExpression:Path=LogViewModel; DataItem='ViewModelLocator' (HashCode=37320431); target element is 'StackPanel' (Name=''); target property is 'DataContext' (type 'Object')
You are binding to the wrong names unfortunately. To fix the binding I had to change the code as below:
ViewModelLocator
public class ViewModelLocator
{
public ViewModelLocator()
{
App thisApp = (App)Application.Current;
LogTextVm = new LogViewModel();
thisApp.LogTextVm = LogTextVm;
}
public LogViewModel LogTextVm { get; set; }
}
MainWindow.xaml
<Grid>
<StackPanel DataContext="{Binding LogTextVm, Source={StaticResource Locator}}">
<TextBlock Text="label:" />
<TextBlock Text="{Binding LogText, Mode=TwoWay}" />
<Button Content="click" Click="Button_Click" />
</StackPanel>
</Grid>
DataContext="{Binding LogTextVm, Source={StaticResource Locator}}"> creates ViewModelLocator instance and binds StackPanel.DataContext to the ViewModelLocator.LogTextVm property.
The <TextBlock Text="{Binding LogText, Mode=TwoWay}" /> binds to the LogText for the current DataContext (which is LogTextVm property value)
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.
I have the following GridView which is bound to my ViewModel
XAML Code:
<GridView SelectionMode="None" ShowsScrollingPlaceholders="False" HorizontalAlignment="Stretch" Margin="2.5 3"
ItemsSource="{Binding ProductList}" Visibility="{Binding IsGridView, Converter={StaticResource BoolToVisibility}}"
SizeChanged="SetListItemsWidth" ScrollViewer.VerticalScrollMode="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled" VerticalAlignment="Stretch">
<GridView.ItemTemplate>
<DataTemplate>
<local:ProductListControl Tag="{Binding id}" Margin="3 0 3 3" Tapped="GotoProduct"/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
Model:
public class ProductC1View : INotifyPropertyChanged
{
public string ScreenTitle
{
get { return _ScreenTitle; }
set
{
_ScreenTitle = value;
OnPropertyChanged("ScreenTitle");
}
}
public ObservableCollection<ProductDisplay> ProductList { get; set; }
}
VIEWMODEL:
class ProductC1ViewModel : INotifyPropertyChanged
{
public ProductC1ViewModel()
{
this.View = new ProductC1View();
}
private async void ApplyFilter(object obj)
{
View.ProductList.Clear();
View.IsProductsAvailable = true;
var lstData = await _objController.GetSubCategoryDetails(View);
foreach (var product in lstData.catalogs)
View.ProductList.Add(_objHelper.FormatProductDisplayDetails(product));
}
}
The GridView is bound to an ObservableCollection. Everything works fine on Intial load or after appending new items to the collection.
But when I clear the items in the collection in case of applying filter on the data and add new items to the collection the GridView doesn't render data. The Underlying Viewmodel(ProductList) contains the data. I can bind it to a ListView and it works. Only for Gridview it doesn't render
And if I change the ItemsPanel of the gridview from ItemsWrapGrid to Stackpanel then its working, but I can't use Stackpanel since I want the list to be displayed by one item stacked next to each other like in Amazon app.
The weird case is it works in Windows 8.1 Phone app but doesn't work in Windows 10. Any help?
Kind of got a temporary fix for this, changed the Itemspanel to wrapgrid and it's working now
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid FlowDirection="LeftToRight" Orientation="Horizontal"></WrapGrid>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
You need to add RaisePropertyChanged (require Mvvmlight) in ProductList setter.
private ObservableCollection<ProductDisplay> _productList;
public ObservableCollection<ProductDisplay> ProductList
{
get
{
return _productList;
}
set
{
_productList = value;
RaisePropertyChanged(()=>ProductList);
}
}
Or staying with just INotifyPropertyChanged you need to implement the PropertyChangedEventHandler ( like in this tutorial):
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private ObservableCollection<ProductDisplay> _productList;
public ObservableCollection<ProductDisplay> ProductList
{
get
{
return _productList;
}
set
{
_productList = value;
NotifyPropertyChanged();
}
}
And you don't necessary need a GridView to display tiles of pictures :
<ListBox ItemsSource="{Binding AllDesignSheetsInDB}" SelectedItem="{Binding CurrentDesignSheet}" BorderBrush="LightGray" BorderThickness="2" ScrollViewer.HorizontalScrollBarVisibility="Disabled" >
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
<ListBox.ItemTemplate>
<DataTemplate>
<local:ProductListControl Tag="{Binding id}" Margin="3 0 3 3" Tapped="GotoProduct"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'm facing a problem in my WPF project at the moment. At this moment I have a Viewmodel which has a Manager (to communicate with the repo).
internal class TicketViewModel
{
private TicketManager mgr;
public IEnumerable<Ticket> List { get; set; }
public TicketViewModel()
{
mgr = new TicketManager();
List = mgr.GetTickets();
}
}
I've managed to bind this list to the Listbox in my MainWindow. The next step is that I need to add an extra ticket to the list and also pass this through the manager. The problem is I need two parameters from some Controls in the MainWindow. From MVVM perspective I need to use bound Commands on e.g. a Button to communicate with the viewmodel as my viewmodel can't/may not access controls from the window. Is using parameterized Commands the way to go here?
The next problem is that the Listbox won't update I guess. This is the code:
<ListBox x:Name="listboxTix" BorderThickness="0" ItemsSource="{Binding List}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" Background="Beige" BorderThickness="2">
<StackPanel Width="250">
<TextBlock Text="{Binding TicketNumber}" />
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding State}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I found that using a CompareableCollection is the way to go here, but then I still have to read all the Tickets again after adding a new Ticket.
Thanks in advance,
Hicy
okay here is the code.
Lets say you have three textboxes on MainWindow(since you have three Textblocks.) so Your MainWindow.xaml looks like
<Window.DataContext>
<local:MyViewModel/>--set's your viewModel
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="250*"/>
<RowDefinition Height="90"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" x:Name="listboxTix" BorderThickness="0" ItemsSource="{Binding List}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" Background="Beige" BorderThickness="2">
<StackPanel Width="250">
<TextBlock Text="{Binding TicketNumber}" />
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding State}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox x:Name="TicketNumber" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=Text}" VerticalAlignment="Top" Width="120"/>
<TextBox x:Name="Text" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=State}" />
<TextBox x:Name="State" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=TicketNumber}" />
<Button Content="Button" Command="{Binding Path=MainCommand}" Grid.Row="2"/>
</Grid>
and I am assuming that you have some class called class Ticket which contain these three members
Class Ticket
{
public int TicketNumber { get; set; }
public string Text { get; set; }
public string State { get; set; }
}
Now in class TicketManager we fill it with some dummy data
class TicketManager
{
ObservableCollection<Ticket> tl = new ObservableCollection<Ticket>();
internal ObservableCollection<Ticket> GetTickets()
{
tl.Add(new Ticket() { State = "State1", Text = "Text1", TicketNumber = 1 });
tl.Add(new Ticket() { State = "State2", Text = "Text2", TicketNumber = 2 });
tl.Add(new Ticket() { State = "State3", Text = "Text3", TicketNumber = 3 });
return tl;
}
}
and in your Mainwindow ViewModel lets call it MyViewModel.cs we add
class MyViewModel:INotifyPropertyChanged
{
private TicketManager mgr;
public ObservableCollection<Ticket> List { get; set; }
private string text;
private string state;
private int ticketNumber;
private readonly DelegateCommand<object> MyButtonCommand;
public Class1()
{
mgr = new TicketManager();
List = mgr.GetTickets();
MyButtonCommand = new DelegateCommand<object>((s) => { AddListToGrid(text, state, ticketNumber); }, (s) => { return !string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(state); });
}
private void AddListToGrid(string text, string state, int ticketNumber)
{
List.Add(new Ticket() {Text=text,State=state,TicketNumber=ticketNumber });
}
public DelegateCommand<object> MainCommand
{
get
{
return MyButtonCommand;
}
}
public string Text
{
get
{
return text;
}
set
{
text = value;
OnPropertyChanged("Text");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
public string State
{
get
{
return state;
}
set
{
state = value;
OnPropertyChanged("State");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
public int TicketNumber
{
get
{
return ticketNumber;
}
set
{
ticketNumber = value;
OnPropertyChanged("TicketNumber");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
public event PropertyChangedEventHandler PropertyChanged;
}
You can Modify the code in anyway you want
This ViewModel implements fewthings which are very important from MVVM point of view
1) INotifyPropertyChanged
2) WPF Delegate Command
P.S:The code is tested and it runs as expected
Don't get hung up on MVVM it is simply a separation of data from a view, and models are shared between the two with a majority of the business logic (on a shared component) should be performed on the VM; it is not a religion just a three tiered data system. IMHO
If your button needs to do an operation, have it make a call, most likely in the code behind, to a method on the VM which handles the business logic, updates the list with the new item and notifies the manager.
I would bind the list in question to an ObservableCollection which can notify upon insert/delete of an item.