I've followed the instruction from here. Nevertheless my binding seems to fail for I have no result in my window. What I want to do is simply to bind a list if items to ListView.
XAML file content:
<Window x:Class="IV_sem___PwSG___WPF_task1.MainWindow"
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"
xmlns:local="clr-namespace:IV_sem___PwSG___WPF_task1"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="800"
MinHeight="500" MinWidth="500">
<Window.Resources>
<DataTemplate x:Key="MyItemTemplate">
<Grid Margin="5" Background="Aqua">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" Grid.Column="0" Grid.Row="0"
FontSize="18" FontFamily="Arial"></TextBlock>
<TextBlock Text="{Binding Description}" Grid.Column="0" Grid.Row="1"
FontSize="14" FontFamily="Arial"></TextBlock>
<TextBlock Text="{Binding Category}" Grid.Column="0" Grid.Row="2"
FontSize="12" FontFamily="Arial"></TextBlock>
<TextBlock Text="{Binding Price}" Grid.Column="1" Grid.RowSpan="1"></TextBlock>
<Button Content="Add to cart" Grid.Column="2" Grid.Row="1"/>
</Grid>
</DataTemplate>
</Window.Resources>
<!--<Window.DataContext>
<local:MainWindow />
</Window.DataContext>-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Menu Grid.Column="0" Grid.Row="0">
<MenuItem Header="File">
<MenuItem Header="Load"/>
<MenuItem Header="Save"/>
<Separator/>
<MenuItem Header="Exit" Click="ExitMenuItem_Click"/>
</MenuItem>
<MenuItem Header="Products">
<MenuItem Header="Add products" Click="AddProductMenuItem_Click"/>
<MenuItem Header="Clear products"/>
</MenuItem>
<MenuItem Header="About" Click="AboutMenuItem_Click"/>
</Menu>
<TabControl Grid.Column="0" Grid.Row="1">
<TabItem Header="Shop">
<!--<Grid>-->
<ListView ItemsSource="{Binding itemList}"
ItemTemplate="{StaticResource MyItemTemplate}"/>
<!--</Grid>-->
</TabItem>
<TabItem Header="Warehouse">
</TabItem>
</TabControl>
</Grid>
.cs file content:
namespace IV_sem___PwSG___WPF_task1{
public partial class MainWindow : Window
{
public List<Item> itemList { get; set; }
public MainWindow()
{
itemList = new List<Item>();
InitializeComponent();
}
private void AboutMenuItem_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("This is simple shop manager.", "About application", MessageBoxButton.OK, MessageBoxImage.Information);
}
private void ExitMenuItem_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
public class Item
{
string Name { get; set; }
string Description { get; set; }
Category Category { get; set; }
double Price { get; set; }
public Item(string a, string b, Category c, double d)
{
Name = a; Description = b; Category = c; Price = d;
}
}
private void AddProductMenuItem_Click(object sender, RoutedEventArgs e)
{
itemList.Add(new Item("Computer", "Computer's description", Category.Electronics, 2499.99));
itemList.Add(new Item("Apple", "Apple's description", Category.Food, 1.99));
itemList.Add(new Item("Computer", "Computer's description", Category.Electronics, 2499.99));
}
}
public enum Category
{
Electronics, Food
}
}
Category is an enum type.
List is a property of MainWindow class.
It should be working according to the link above.
The code in the question has three problems.
1. The ListView.ItemsSource binding
The program does not set the DataContext for the ListView or one of its parent containers. So the binding in <ListView ItemsSource="{Binding itemList}" ... /> cannot be resolved.
Since itemList is a property of the MainWindow itself, as a quick fix i suggest to bind the MainWindow DataContext to the MainWindow itself:
<Window x:Class="IV_sem___PwSG___WPF_task1.MainWindow"
...
...
x:Name="Self"
DataContext="{Binding ElementName=Self}"
>
2. The itemList
itemList is just a simple List<T> collection. List<T> does not have a built-in mechanism to inform the ListView and the binding engine whenever items have been added to or removed from the list. When the program adds items to the itemList in the AddProductMenuItem_Click handler, the ListView will not update, simply because it is not being notified that the itemList has been changed.
The fix is rather simple: Use ObservableCollection<T> instead of List<T>. ObservableCollection provides notifications about item addition and removal. These notifications are being understood by WPF and enable the ListView to refresh its view whenever the collection has been altered.
3. The Item class
The itemList collection contains Item objects. UI elements in the item template used by the ListView bind to properties of these Item objects. However, the properties of the Item class are not public. The binding to these properties fails if they are not public.
The fix, again, is rather straightforward: Make all properties participating in bindings public.
Use Observable collection instead of List as below
public ObservableCollection<Item> itemList { get; set; }
because Observable Collection implemented using INotifyCollectionChanged, INotifyPropertyChanged it's notify collection change event and update UI.
Related
I am still new to WPF and MVVM and am trying to keep the seperation between View and View Model.
i have an app, essentially a projects task list app, in this i create projects and within each project i can create a set of tasks. Most is working well, but essentially i cannot get a command binding on a checkbox in a user control to work using DP, inherited datacontext etc. i always ge a binding failed error when running the app. i am trying to bing to a command in the viewmodel of the view which contains the user controls.
i created a user control to pull the task data together in the view, the command is on the checkbox
<UserControl x:Class="TaskProjectApp.Controls.TaskControl"
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:TaskProjectApp.Controls"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="300">
<Grid Background="LightBlue">
<StackPanel Margin="5,5,5,5">
<TextBlock x:Name="titleTB"
Text="title"
FontSize="20"
FontWeight="Bold"/>
<TextBlock x:Name="DescriptionTB"
Text="description.."
FontSize="15"
Foreground="DodgerBlue"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="priority"
Text="0"
FontSize="15"
FontStyle="Italic"/>
<CheckBox Grid.Column="1"
x:Name="iscomplete"
Command="{Binding SetComplete}"/>
</Grid>
</StackPanel>
</Grid>
</UserControl>
in the user control code behind i have set the DP and the set text function is working
namespace TaskProjectApp.Controls
{
/// <summary>
/// Interaction logic for TaskControl.xaml
/// </summary>
public partial class TaskControl : UserControl
{
public UserTask Task
{
get { return (UserTask)GetValue(TaskProperty); }
set { SetValue(TaskProperty, value); }
}
// Using a DependencyProperty as the backing store for Task. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TaskProperty =
DependencyProperty.Register("Task", typeof(UserTask), typeof(TaskControl), new PropertyMetadata(new UserTask()
{
Title = "title",
Description = "none",
Comments = "none"
}, SetText));
private static void SetText(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TaskControl task = d as TaskControl;
if (task != null)
{
task.titleTB.Text = (e.NewValue as UserTask).Title;
task.DescriptionTB.Text = (e.NewValue as UserTask).Description;
task.priority.Text = (e.NewValue as UserTask).Priority.ToString();
task.iscomplete.IsChecked = (e.NewValue as UserTask).IsComplete;
}
}
public TaskControl()
{
InitializeComponent();
}
}
}
now to make this work i set the binding of the user control in the window as so, the listview takes the usercontrols and implements the observable collection of tasks.
<Window x:Class="TaskProjectApp.Views.ProjectsView"
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"
xmlns:local="clr-namespace:TaskProjectApp.Views"
xmlns:uc="clr-namespace:TaskProjectApp.Controls"
mc:Ignorable="d"
Title="ProjectsView" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<uc:ProjectControl Project="{Binding UserProject}" />
<StackPanel Grid.Row="1">
<TextBlock Text="Task List"/>
<ListView ItemsSource="{Binding Tasks}"
SelectedItem="{Binding SelectedTask}">
<ListView.ItemTemplate>
<DataTemplate>
<uc:TaskControl Task="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Add Task"
Command="{Binding NewProjectTask}"/>
<Button Content="Delete Task"
Command="{Binding DeleteProjectTask}"/>
</StackPanel>
</Grid>
</Window>
this seems to completely stop me using the command, i set the datacontext in the code behind, to the whole window
public partial class ProjectsView : Window
{
public ProjectViewModel ProjectViewModel { get; set; }
public ProjectsView()
{
InitializeComponent();
}
public ProjectsView(UserProject userProject)
{
InitializeComponent();
ProjectViewModel = new ProjectViewModel(userProject);
DataContext = ProjectViewModel;
}
}
and reading trying to solve this has shown that the usercontrol should inherit the datacontext of the parent window.
i have seen solutions using relative paths and DPs for the commands as well as people saying these are not needed just let the inherited datacontext handle it.
but i have tried all three an neither works.
the interface shows me a message box saying no datacontext found, although i notice this is the case when you set the datacontext in code behind and not the xaml.
the SetCommand is created in the projects view model and its a property not a field as i have seen this fail for that reason too.
namespace TaskProjectApp.ViewModels
{
public class ProjectViewModel
{
public UserProject UserProject { get; set; }
public ProjectViewModel(UserProject userProject)
{
UserProject = userProject;
Tasks = new ObservableCollection<UserTask>();
NewProjectTask = new NewProjectTaskCommand(this);
DeleteProjectTask = new DeleteProjectTaskCommand(this);
SetComplete = new SetCompleteCommand();
ReadTaskDatabase();
}
public ObservableCollection<UserTask> Tasks { get; set; }
public NewProjectTaskCommand NewProjectTask { get; set; }
public DeleteProjectTaskCommand DeleteProjectTask { get; set; }
public SetCompleteCommand SetComplete { get; set; }
public UserTask SelectedTask { get; set; }
public void ReadTaskDatabase()
{
List<UserTask> list = new List<UserTask>();
using (SQLiteConnection newConnection = new SQLiteConnection(App.databasePath))
{
newConnection.CreateTable<UserTask>();
list = newConnection.Table<UserTask>().ToList().OrderBy(c => c.Title).ToList();
}
Tasks.Clear();
foreach (UserTask ut in list)
{
if (ut.ProjectId == UserProject.Id)
{
Tasks.Add(ut);
}
}
}
}
}
if anyone can point out where i am going wrong tat will be great as i fear i am now not seeing the wood for the trees.
I found the solution thanks to Ash link Binding to Window.DataContext.ViewModelCommand inside a ItemsControl not sure how i missed it, maybe wrong key words. anyway because the datacontext of the usercontrol is being made into my data class in the observable list Tasks
<StackPanel Grid.Row="1">
<TextBlock Text="Task List"/>
<ListView ItemsSource="{Binding Tasks}"
SelectedItem="{Binding SelectedTask}">
<ListView.ItemTemplate>
<DataTemplate>
<uc:TaskControl Task="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Add Task"
Command="{Binding NewProjectTask}"/>
<Button Content="Delete Task"
Command="{Binding DeleteProjectTask}"/>
</StackPanel>
you need to use a relative path inside the user control to look up past the ItemTemplate to the ListView itself as this uses the viewmodel data context to bind to, so has access to the right level
<UserControl x:Class="TaskProjectApp.Controls.TaskControl"
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:TaskProjectApp.Controls"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="300">
<Grid Background="LightBlue">
<StackPanel Margin="5,5,5,5">
<TextBlock x:Name="titleTB"
Text="title"
FontSize="20"
FontWeight="Bold"/>
<TextBlock x:Name="DescriptionTB"
Text="description.."
FontSize="15"
Foreground="DodgerBlue"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="priority"
Text="0"
FontSize="15"
FontStyle="Italic"/>
<CheckBox Grid.Column="1"
x:Name="iscomplete"
Command="{Binding DataContext.SetComplete, RelativeSource={RelativeSource AncestorType=ListView}}"/>
</Grid>
</StackPanel>
</Grid>
</UserControl>
this might be limiting in future as it measn the usercontrol will look for a listview to bind the command, but it solves the immediate issue.
I have both a DataGrid and a ComboBox on wpf UserControl.
namespace ApSap
{
public partial class DocumentView : UserControl
{
public Document document;
public DocumentView(Document selectedDoc)
{
document = selectedDoc;
InitializeComponent();
DocBrowser.Navigate(document.FilePath);
// shows only empty rows
SapGrid.ItemsSource = document.SapDocNumbers;
// shows list of values correctly
Combo.ItemsSource = document.SapDocNumbers;
}
}
}
The combo Box correctly displays the content of the public property "SapDocNumbers" (a list of integers),
However the datagrid only displays empty rows, albiet the correct number of them.
the XAML is as follows:
<UserControl x:Class="ApSap.DocumentView"
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"
mc:Ignorable="d"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
>
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" Grid.Row="1">
<DataGrid AutoGenerateColumns="True" x:Name="SapGrid" Margin="10,10,10,10" >
</DataGrid>
<Button x:Name="CreateInvoice" Content="Create Invoice" Margin="10,10,10,10" />
<Button x:Name="Save" Content="Save and Exit" Margin="10,10,10,10" />
<ComboBox x:Name="Combo" Margin="10,10,10,10" />
</StackPanel>
</Grid>
Is there anything I am missing from XAML grid definition that would mean the combo works correctly, but the datagrid does not?
as requested here is the definition of the class:
public class Document : INotifyPropertyChanged
{
private int _docID;
private List<Int64> _sapDocNumbers;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public int DocID
{
get { return _docID; }
set
{
if (value != _docID)
{
_docID = value;
NotifyPropertyChanged();
}
}
}
public List<Int64> SapDocNumbers
{
get { return _sapDocNumbers; }
set
{
if (value != _sapDocNumbers)
{
_sapDocNumbers = value;
NotifyPropertyChanged();
}
}
}
Thank you
The answer to your question depends on the implementation of the collection item type.
ComboBox uses ToString () by default to represent the item.
And DataGrid renders the properties of the element - for each property there is a separate column.
If the element type has no properties, the DataGrid will not create columns and will not display anything.
For a more precise answer, show the type of the collection and the implementation of the type of its element.
Completion of the answer in connection with the clarification of the question:
You want to display the ulong collection.
This type has no properties and therefore autogenerating columns in the DataGrid cannot create columns.
For your task you need:
<DataGrid AutoGenerateColumns="False" x:Name="SapGrid" Margin="10" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Mode=OneWay}" Header="Number"/>
</DataGrid.Columns>
</DataGrid>
one final question, How do i get a blank row on the DataGrid so the user can add their own SapDocNumbers to the list ?
The item of the collection goes to the Data Context of the DataGrid row.
UI elements cannot edit their DataContext.
Therefore, for a string, you need to create a reference ty with a property of the desired type and edit this property.
And this type must have a default constructor.
Since the list can be used in several bindings, the type of the list should not be a simple collection, but an observable collection.
For examples used class BaseInpc
using Simplified;
namespace ApSap
{
public class DocumentRow : BaseInpc
{
private long _sapDocNumber;
public long SapDocNumber { get => _sapDocNumber; set => Set(ref _sapDocNumber, value); }
public DocumentRow(long sapDocNumber) => SapDocNumber = sapDocNumber;
public DocumentRow() { }
}
}
using Simplified;
using System.Collections.ObjectModel;
namespace ApSap
{
public class Document : BaseInpc
{
private int _docID;
public int DocID { get => _docID; set => Set(ref _docID, value); }
public ObservableCollection<DocumentRow> SapDocNumbers { get; }
= new ObservableCollection<DocumentRow>();
public Document()
{
}
public Document(int docID, params long[] sapDocNumbers)
{
DocID = docID;
foreach (var number in sapDocNumbers)
SapDocNumbers.Add(new DocumentRow(number));
}
public static Document ExampleInstance { get; } = new Document(123, 4, 5, 6, 7, 8, 9, 0);
}
}
<Window x:Class="ApSap.DocumentWindow"
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"
xmlns:local="clr-namespace:ApSap"
mc:Ignorable="d"
Title="DocumentWindow" Height="450" Width="800"
DataContext="{x:Static local:Document.ExampleInstance}">
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" Grid.Row="1">
<TextBlock Margin="10,10,10,0">
<Run Text="DocID:"/>
<Run Text="{Binding DocID}"/>
</TextBlock>
<DataGrid AutoGenerateColumns="True" Margin="10"
ItemsSource="{Binding SapDocNumbers}"/>
<Button x:Name="CreateInvoice" Content="Create Invoice" Margin="10" />
<Button x:Name="Save" Content="Save and Exit" Margin="10" />
<ComboBox x:Name="Combo" Margin="10" />
</StackPanel>
<DataGrid Grid.Row="1" AutoGenerateColumns="True" Margin="10"
ItemsSource="{Binding SapDocNumbers}" VerticalAlignment="Top"/>
</Grid>
</Window>
P.S. Learn to set the Data Context and bindings to it. Using the names of UI elements, referring to them in Sharp is most often very bad code.
This question already has answers here:
How do I get INotifyPropertyChanged to update bound items from a list stored in SQL?
(1 answer)
C# WPF ViewModel for a class with List
(1 answer)
Closed 3 years ago.
I'm making an application in WPF and wanted to bind a List<string> to an ItemsControl using a DataTemplate. However, when I try to Add elements to the list, the changes aren't reflected in my interface.
I've tried searching for answers relating to collections and dependency properties, but no one seems to even acknowledge that this is a problem. The documentation on Microsoft's websites don't provide any help either.
public partial class MainWindow : Window {
public static readonly DependencyProperty PathsProperty = DependencyProperty.Register(
"Paths",
typeof(List<string>),
typeof(MainWindow),
new PropertyMetadata(new List<string>())
);
public IList<string> Paths {
get { return GetValue(PhotoPathsProperty) as IList<string>; }
set { SetValue(PhotoPathsProperty, value); }
}
public MainWindow() {
DataContext = this;
InitializeComponent();
WatermarkPath = "filepath";
PhotoPaths = new List<string>();
}
private void Button_Click_1(object sender, RoutedEventArgs e) {
OpenFileDialog dialog = new OpenFileDialog {
Filter = ".PNG|*.png|.JPG|*.jpg",
Title = "Open Photo Image(s)"
};
if (dialog.ShowDialog() == true) {
foreach (var path in dialog.FileNames) {
PhotoPaths.Add(path);
}
}
}
}
<Window x:Class="PE_WaterMarker.MainWindow"
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"
xmlns:local="clr-namespace:PE_WaterMarker"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<DockPanel>
<GroupBox x:Name="groupBox" DockPanel.Dock="Top" Header="Watermark" Margin="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="textBox" Grid.Column="0" Text="{Binding Path=WatermarkPath}" VerticalAlignment="Center"/>
<Button x:Name="button" Grid.Column="1" Content="Browse" Click="Button_Click" VerticalAlignment="Center"/>
</Grid>
</GroupBox>
<GroupBox Header="Images">
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=Paths}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding Path=.}"/>
<Button Grid.Column="1" Content="Browse"/>
<Button Grid.Column="2" Content="Remove"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Add Image(s)" Click="Button_Click_1"/>
</StackPanel>
</GroupBox>
</DockPanel>
</Window>
Let me begin with apologizing for the perhaps somewhat vague title, I'm having a hard time explaining my problem! Perhaps this is why I'm hardly getting any Google results, with this post being the closest to my problem (I think): How to bind to a property of the ViewModel from within a GridView
Anyways, I have a list of news articles that are being dynamically generated. The user has the option to press a "Star"-button in order to add an article to his/her favorites list. This "Star"-button should thus only be visible when the user is logged in.
I'm trying to achieve this by setting the Visibility of the Button to a property called IsLoggedIn inside my ViewModel. However, because this is happening inside my ListView it's trying to find the property IsLoggedIn inside of Article instead of the ViewModel.
So I guess my questions boils down to: How can I bind to a ViewModel property inside of a Databound ListView?
<ListView ItemsSource="{x:Bind VM.Articles, Mode=OneWay}" ItemClick="DebuggableListView_ItemClick" IsItemClickEnabled="True" SelectionMode="None" Grid.Row="1" VerticalAlignment="Top">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:Article">
<Grid Margin="0,10,10,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="4*"/>
</Grid.ColumnDefinitions>
<Image Source="{Binding Image}" Margin="0,0,10,0" Grid.Column="0" VerticalAlignment="Top" ImageFailed="Image_ImageFailed"/>
<Button Visibility="{x:Bind VM.IsLoggedIn, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}" FontFamily="Segoe MDL2 Assets" Content="" FontSize="30" Background="Transparent" Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
<StackPanel Grid.Column="1">
<TextBlock TextWrapping="Wrap" FontWeight="Bold" Text="{Binding Title}"/>
<TextBlock TextWrapping="Wrap" Text="{Binding Summary}"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Requested Article class:
public sealed class Article
{
public int Id { get; set; }
public int Feed { get; set; }
public string Title { get; set; }
public string Summary { get; set; }
public DateTime PublishDate { get; set; }
public string Image { get; set; }
public string Url { get; set; }
public string[] Related { get; set; }
public Category[] Categories { get; set; }
public bool IsLiked { get; set; }
}
Okay, so currently I got it working by having a property which gets the singleton of my VM, however I'm sure there has to be a cleaner way to get something simple like this working. I've added a sample rar (OneDrive link: https://1drv.ms/u/s!Ar4fOTiwmGYnyWbwRY6rM0eFsL9x) which has a list, a ViewModel, some dummy data and a Visibility property inside the VM. If you can get it working without my dirty method please feel free to commit as an answer.
This type problem can best be solved using relative binding. Rather than binding directly to the current DataContext, i.e. this case the individual item in the ListView's ItemsSource, you can bind to a property of any ancestor element.
Given a simple ViewModel class
public class ViewModel: ViewModelBase
{
private bool _isLoggedIn;
public bool IsLoggedIn
{
get { return _isLoggedIn; }
set
{
_isLoggedIn = value;
RaisePropertyChanged(() => IsLoggedIn);
}
}
public IEnumerable<string> Items
{ get { return new[] {"One", "Two", "Theee", "Four", "Five"}; } }
}
the View can then be defined as
<Window x:Class="ParentBindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ParentBindingTest"
Title="MainWindow"
Width="300" Height="400">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<CheckBox Margin="16,8" HorizontalAlignment="Left" Content="Logged In?" IsChecked="{Binding IsLoggedIn, Mode=TwoWay}" />
<ListView Grid.Row="1" Margin="16,8" ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Visibility="{Binding Path=DataContext.IsLoggedIn, RelativeSource={RelativeSource AncestorType={x:Type ListView}}, Converter={StaticResource BooleanToVisibilityConverter}}">
<Image Source="Images\remove.png" />
</Button>
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
Note how in the item DataTemplate, the TextBlock's Text property is bound directly to the item.
However the Button's Visibility is bound not to a propery of it's own DataContext, but to that of the ListView itself, using relative binding.
I'm tring to databind properties of an ObservableCollection to a ListBox (Just the Title property for example).
By clicking on one of the ListItem (with an event ), i'd like to display all the properties of the Collection into a StackPanel. After many tries, I still don't know how can I figure it out...
Here is my code behind :
public partial class TestListView : Window
{
public TestListView()
{
ObservableCollection<Programme> pgr = new ObservableCollection<Programme>();
pgr = readfile();
InitializeComponent();
}
public class Programme
{
public String Title { get; set; }
public String Date { get; set; }
public String Chaine { get; set; }
public Programme(String Title, String Date, String Chaine)
{
this.Title = Title;
this.Date = Date;
this.Chaine = Chaine;
}
}
Here is my XAML :
<Window x:Class="Test.TestListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test;assembly=Test"
Title="TestListView" Height="500" Width="1000" x:Name="Window">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="249*"/>
<ColumnDefinition Width="743*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20*"/>
<RowDefinition Height="428*"/>
<RowDefinition Height="21*"/>
</Grid.RowDefinitions>
<ListBox Name="l1" ItemsSource="{Binding pgr}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" Grid.Row="1">
<TextBox Margin="343,0,0,0" x:Name="Recherche"></TextBox>
<Button Height="37" Margin="669,0,0,0" ></Button>
<TextBlock x:Name="t1" Margin="214,0,293,0" Height="33" />
</StackPanel>
</Grid>
</Window>
You need to bind your data to (public) properties. Also, you don't need to use ObservableCollection; any selection changes will be picked up anyhow.
Here's a working sample, with layout and other bits and pieces changed to make it compile for me:
public partial class MainWindow
{
public IList<Programme> pgr { get; }
public MainWindow()
{
pgr = new List<Programme>
{
new Programme("First", "FirstDate", "FirstChaine"),
new Programme("Second", "SecondDate", "SecondChaine"),
new Programme("Third", "ThirdDate", "ThirdChaine"),
};
InitializeComponent();
}
public class Programme
{
// No changes
}
}
...and the XAML:
<Window
x:Name="self"
x:Class="WpfApplication1.MainWindow"
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"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow"
Height="350"
Width="525">
<StackPanel DataContext="{Binding ElementName=self}" Orientation="Horizontal">
<ListBox Name="l1" ItemsSource="{Binding pgr}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Orientation="Vertical">
<TextBox Margin="10" Text="{Binding ElementName=l1,Path=SelectedItem.Title}" />
<Button Height="37" Margin="0" Content="{Binding ElementName=l1,Path=SelectedItem.Date}"></Button>
<TextBlock Margin="10" Height="33" Text="{Binding ElementName=l1,Path=SelectedItem.Chaine}" />
</StackPanel>
</StackPanel>
</Window>
Note the bindings that reference the selected item:
Text="{Binding ElementName=l1,Path=SelectedItem.Title}"