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.
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 an UserControl. At the top, there is a global parameter, bound to a static property in the class MultiSliceCommand. Below, there is a TabControl, populated by a Template and bound to public static ObservableCollection<GroupContainer> groups, also a property in MultiSliceCommand. GroupContainer contains various properties, mainly doubles, ints etc., displayed and editable in textboxes in the TabItems.
When I now change a value in TabItem, the corresponding property in the correct element of groups is set.
However, when I close & reopen the dialog, the all the GroupContainers in groups are reset to their defaults - even the properties not bound at any point to the dialog.
Changes to the global variables (outside of the TabControl) are preserved correctly. Changes to the TabControl are also preserved correctly if I remove the binding to the global variables - in explicit, if I remove the lines <local:MultiSliceCommand x:Key="mutliSliceCommand" /> and <TextBox x:Name="Mm_Per_Package" Text="{Binding Source={StaticResource mutliSliceCommand}, Path=Mm_Per_Package}" />
How can I change the bindings to preserve the changes to the global variable as well as the contents of the Tabs when closing & reopening the dialog?
The Xaml File:
<UserControl.Resources>
<DataTemplate x:Key="HeaderTemplate">
<Label Content="{Binding Group_Name}" />
</DataTemplate>
<local:MultiSliceCommand x:Key="mutliSliceCommand" />
<DataTemplate x:Key="ItemTemplate">
<Grid>
<TextBox x:Name="_length" Text="{Binding Path=Length, UpdateSourceTrigger=PropertyChanged, Delay=0}" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<ScrollViewer>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<GroupBox
Header="Global Parameters"
Grid.Row="0"
Grid.Column="0"
>
<Grid Height="Auto" Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox x:Name="Mm_Per_Package" Text="{Binding Source={StaticResource mutliSliceCommand}, Path=Mm_Per_Package}" />
</Grid>
</GroupBox>
<GroupBox
Header="Materials"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
>
<TabControl x:Name="TabControl1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemTemplate="{StaticResource HeaderTemplate}"
ContentTemplate="{StaticResource ItemTemplate}"
/>
</GroupBox>
<!--
<Button Content="Save settings"
Grid.Row="2"
HorizontalAlignment="Right"
Margin="10,10,0,0"
VerticalAlignment="Top"
Width="75"
Click="Btn_Save" />-->
</Grid>
</ScrollViewer>
The Class MultiSliceCommand
public class MultiSliceCommand
{
public static ObservableCollection<GroupContainer> groups { get; set; }
private static double _mm_per_package { get; set; } = 0;
public static double Mm_Per_Package
{
get { return _mm_per_package; }
set { _mm_per_package = value < 0 ? 0 : value; }
}
public MultiSliceCommand()
{
groups = new ObservableCollection<GroupContainer>
{
new GroupContainer("Group 1"),
new GroupContainer("Group 1"),
new GroupContainer("Group 3")
};
}
}
The class ObjectContainer
public class GroupContainer : INotifyPropertyChanged
{
private double _length { get; set; } = 0;
public double Length
{
get { return _length; }
set { _length = value < 0 ? 0 : value; NotifyPropertyChanged("Min_Vector_Length"); }
}
// Methods
public GroupContainer(string group_name)
{
}
// Helper Stuff
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string sProp)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(sProp));
}
}
}
Ok, fixed it with an (somewhat dirty) hack:
I just outsourced the global variable to its own class, and bind the xaml to this class. In MultiSliceCommand, I use getter / setter on the property to just relay the value from the "isolation class"
Isolation class:
public class xaml_backend_variables
{
private static double _mm_per_package = 0;
public static double Mm_Per_Package
{
get { return _mm_per_package; }
set { _mm_per_package = value < 0 ? 0 : value; }
}
public xaml_backend_variables()
{
}
}
MultiSliceCommand
public static double Mm_Per_Package
{
get { return xaml_backend_variables.Mm_Per_Package; }
set { xaml_backend_variables.Mm_Per_Package = value; }
}
XAML Modifications
....
<local:xaml_backend_variables x:Key="xaml_backend_variables" />
....
<TextBox x:Name="Mm_Per_Package" Text="{Binding Source={StaticResource xaml_backend_variables}, Path=Mm_Per_Package}" />
But now all values are preserved correctly when closing and reopening the dialog.
Still, if someone has an explanation why this happens and what would be the correct / elegant way to solve this, I would like very much to know!
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.
I have tried data binding in WPF.
But it is showing few errors.Please help me.
I am attaching the code.I have create a simple text block and tried to bind the string. Also I want to know how Windows.datacontext works? In my code it is giving an error. please help me out.
Xaml code
<Window x:Class="Shweta.DataBinding"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataBinding" Height="300" Width="300">
<Window.DataContext>
<l:DataBinding />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="54*" />
<ColumnDefinition Width="224*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="59*" />
<RowDefinition Height="202*" />
</Grid.RowDefinitions>
<Grid Grid.Column="1" Grid.Row="1">
<TextBlock Height="23" HorizontalAlignment="Left" Margin="10,10,0,0" Name="textBlock1" Text="{Binding TextString, TargetNullValue=Test}" VerticalAlignment="Top" Width="68" />
</Grid>
</Grid>
</Window>
**Code behind**
namespace Shweta
{
public partial class DataBinding : Window
{
public DataBinding()
{
InitializeComponent();
Setupviewmodel();
}
private void Setupviewmodel
{
TextString="this worked";
}
public string TextString{get;set;}
}
}
Okay so first of all read the error messages ... It clearly says that l is not defined in XAML but still you're trying to use it : <l:DataBinding />...
Fix this by declaring l in your XAML :
<Window x:Class="Shweta.DataBinding"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:l="<your l declaration"/>
Another thing is that you haven't implemented INotifyPropertyChanged so you'r value wont get updated anyway.
Implement this like such :
public partial class DataBinding : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
if ( PropertyChanged != null )
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
string text;
public string TextString
{
get { return text; }
set { text = value; NotifyPropertyChanged(); }
}
public DataBinding()
: base()
{
InitializeComponent();
Setupviewmodel();
// as #Nahuel Ianni stated, he has to set DataContext to CodeBehind
// in order to be able to get bindings work
DataContext = this; // <-- only if not binded before
}
public void Setupviewmodel() // forgot to to place ()
// produced error : `A get or set accessor expected`
{
TextString = "this worked";
}
}
Yet another thing is that you have to specify DataContext only when it's not the same as your code behind so you do not need this part :
<Window.DataContext>
<l:DataBinding />
</Window.DataContext>
You are not specifying the DataContext correctly as you are trying to set it up on XAML by using a namespace that has not been declared. For more info on XAML namespaces, check the following link.
In your example it would be on the xaml side:
<Window x:Class="Shweta.DataBinding"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataBinding" Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="54*" />
<ColumnDefinition Width="224*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="59*" />
<RowDefinition Height="202*" />
</Grid.RowDefinitions>
<Grid Grid.Column="1" Grid.Row="1">
<TextBlock Height="23" HorizontalAlignment="Left" Margin="10,10,0,0" Name="textBlock1" Text="{Binding TextString, TargetNullValue=Test}" VerticalAlignment="Top" Width="68" />
</Grid>
</Grid>
</Window>
And in your code behind:
namespace Shweta
{
public partial class DataBinding : Window
{
public DataBinding()
{
InitializeComponent();
this.DataContext = this; // Pay attention to this line!
Setupviewmodel();
}
private void Setupviewmodel()
{
TextString="this worked";
}
public string TextString{get;set;}
}
}
The difference with the original version is that I'm not specifying the DataContext on XAML but on the code behind itself.
The DataContext can be considered as the place where the view will retrieve the information from. When in doubt, please refer to this MSDN article or you could learn about the MVVM pattern which is the pillar of working with XAML.
In order to make this work you have to set the DataContext properly. I'd suggest to create a viewmodel class and bind to that. Also I initialized the binding in the codebehind because your namespaces are missing. You can do that in xaml aswell. For now to give you something to work with try this for your codebehind:
public partial class DataBinding : Window
{
public DataBinding()
{
InitializeComponent();
DataContext = new DataBindingViewModel();
}
}
public class DataBindingViewModel
{
public DataBindingViewModel()
{
Setupviewmodel();
}
private void Setupviewmodel()
{
TextString = "this worked";
}
public string TextString { get; set; }
}
And change your view to this:
<Window x:Class="Shweta.DataBinding"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataBinding" Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="54*" />
<ColumnDefinition Width="224*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="59*" />
<RowDefinition Height="202*" />
</Grid.RowDefinitions>
<Grid Grid.Column="1" Grid.Row="1">
<TextBlock Height="23" HorizontalAlignment="Left" Margin="10,10,0,0" Name="textBlock1" Text="{Binding TextString, TargetNullValue=Test}" VerticalAlignment="Top" Width="68" />
</Grid>
</Grid>
Note that the Text property will only be set at initialization. If you want DataBinding at runtime your DataBindingViewModel will have to implememnt INPC and throw the PropertyChanged Event after setting the property bound to.
I am practicing MVVM application and i am beginner, what i am trying to do is, I have 3 rows in a grid, the first row will contain 3 labels and 3 corresponding buttons. And the second row will contain a button to save the data entered in that textboxes in first row. The third row will contain a datagrid with the same number and type of textboxes (three).
Look can be seen here http://prntscr.com/9v2336
The user will enter the data in first row and then he will press save button in second row and then he must find the written information in the corresponding datagrid columns.
My try is here (Entire Code):
View:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding TextName}" Height="20" Width="80" HorizontalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding RollNumber}" Height="20" Width="80"></TextBox>
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Class}" Height="20" Width="80"></TextBox>
<Label Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center">Name</Label>
<Label Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">RollNumber</Label>
<Label Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center">Class</Label>
</Grid>
<Grid Grid.Row="1" >
<Button Width="80" Height="20" Command="{Binding saveStudentRecord}"> Save</Button>
</Grid>
<Grid Grid.Row="2">
<DataGrid ItemsSource="{Binding DGrid}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding dgName}" Width="150"></DataGridTextColumn>
<DataGridTextColumn Header="Rollnumber" Binding="{Binding dgRollnumber}" Width="150"></DataGridTextColumn>
<DataGridTextColumn Header="Class" Binding="{Binding dgClass}" Width="150"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
</Window>
Model:
class Model
{
private string textName;
public string TextName
{
get { return textName; }
}
private string rollNumber;
public string RollNumber
{
get { return rollNumber; }
}
private string cclass;
public string Class
{
get { return cclass; }
}
}
ViewModel:
class ViewModel
{
public bool canExecute { get; set; }
private RelayCommand saveStudentRecord;
private ObservableCollection<Model> dGrid;
public ViewModel()
{
}
private void MyAction()
{
//What to do here to pass all that data to the datagrid corresponding columns
}
}
Where i have problem ?
I have designed the entire body but i am not able to find the logic that how would i assign the entered data in text boxes to corresponding data-grid columns on button click event and binding themusing only MVVM.
Should be simple as adding a new Model to your ObservableCollection<Model> DGrid:
class ViewModel
{
public bool canExecute { get; set; }
private RelayCommand saveStudentRecord;
private ObservableCollection<Model> dGrid;
public ViewModel()
{
dGrid = new ObservableCollection<Model>();
}
private void MyAction()
{
dGrid.Add(new Model(){
TextName = valueOfTextTextBox,
RollNumber = valueOfRollNumberTextBox,
Class = valueOfClassTextBox
});
}
}
Thing to remember here:
dGrid should be a public property, so you can use Databinding with it, e.g.:
public ObservableCollection<Model> dGrid {
get;
private set;
}
EDIT based on the question in commments:
You need to bind the text property of TextBoxes to a property on you ViewModel. As ModelClass holds that kind of info, I would do:
class ViewModel
{
public bool canExecute { get; set; }
private RelayCommand saveStudentRecord;
private ObservableCollection<Model> dGrid;
public ViewModel()
{
dGrid = new ObservableCollection<Model>();
}
public Model EditedModel {
get {
return _editedModel;
}
set {
_editedModel = value;
SignalPropertyChanged("EditedModel");
}
}
private void MyAction()
{
dGrid.Add(EditedModel);
EditedModel = new Model();
}
void SignalPropertyChanged(string propertyName){
if(propertyChanged !=null){
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Of course now your ViewModel and Model need to implement INotifyPropertyChanged interface to notify view of the changes
class Model : INotifyPropertyChanged
{
private string textName;
public string TextName
{
get { return textName; }
set {
textName = value;
SignalPropertyChanged("TextName");
}
}
private string rollNumber;
public string RollNumber
{
get { return rollNumber; }
set {
rollNumber= value;
SignalPropertyChanged("RollNumber");
}
}
private string cclass;
public string Class
{
get { return cclass; }
set {
cclass= value;
SignalPropertyChanged("Class");
}
}
public event PropertyChangedEventHandler propertyChanged;
void SignalPropertyChanged(string propertyName){
if(propertyChanged !=null){
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
EDIT2 - forgot to add XAML part :) You need to bind the textboxes to a new property
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding EditedModel.TextName}" Height="20" Width="80" HorizontalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding EditedModel.RollNumber}" Height="20" Width="80"></TextBox>
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding EditedModel.Class}" Height="20" Width="80"></TextBox>
<Label Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center">Name</Label>
<Label Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">RollNumber</Label>
<Label Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center">Class</Label>
</Grid>
<Grid Grid.Row="1" >
<Button Width="80" Height="20" Command="{Binding saveStudentRecord}"> Save</Button>
</Grid>
<Grid Grid.Row="2">
<DataGrid ItemsSource="{Binding DGrid}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding dgName}" Width="150"></DataGridTextColumn>
<DataGridTextColumn Header="Rollnumber" Binding="{Binding dgRollnumber}" Width="150"></DataGridTextColumn>
<DataGridTextColumn Header="Class" Binding="{Binding dgClass}" Width="150"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
</Window>