I was going to create this with windows forms but was told that wpf mvvm would be better. I am new to c# and have been researching mvvm and wpf.
I am now working on my viewmodel to work with both the view and the model. There is no database.
My problem:
how do I correctly bind the view to the viewmodel. I am missing a itemssource or localsource code somewhere in my xaml but I also do not understand how the itemsource works. Where in the viewmodel is the itemsource declared and how. I have been googling for a good answer but still have not found one that makes it click for me.
I also know there is an INotifyChange type property and i have seen some code examples but dont fully understand it, it just has not clicked for me.
Currently:
I have a view created in xaml which is the first code below. I then created a class for a scan which is the second group of code below in c# (i know the get set methods could be improved but i was following a tutorial).
The user with the scan gun is not going to be looking at the screen when they are scanning. I want to be able to go in order so first scan fills in the first text box, second scan fills the second text box and if needed they will fill in the count.
Extra info:
The bottom part (dataview) is a temp table for showing previous scans but I can figure that our later. Most important part is being able to get the scans and do something with them.
The scans will be keyboardwedge (sends characters like being typed w/ an enter key at the end) but later i am planning on making them serial com port so this program can run in the background.
Note: I know i gave a lot of detail that is probobly not needed for the small current problem but just wanted to be clear.
<Window x:Class="ScanningV2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="700">
<DockPanel LastChildFill="True">
<Grid x:Name="LayoutRoot" DockPanel.Dock="Top" Height="100" Background="#FFFFFF" Margin="2,2,2,2">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Content="Scan" Grid.ColumnSpan="1" Margin="2,2,2.2,2" />
<Label Content="Operator Barcode" Grid.Column="1" HorizontalAlignment="Left" Margin="50,20,0,0" VerticalAlignment="Top" Width="120" />
<Label Content="MO/Task Barcode" Grid.Column="1" HorizontalAlignment="Left" Margin="200,20,0,0" VerticalAlignment="Top" Width="120" />
<Label Content="Quantity" Grid.Column="1" HorizontalAlignment="Left" Margin="350,20,0,0" VerticalAlignment="Top" Width="120" />
<TextBox Grid.Column="1" HorizontalAlignment="Left" Margin="50,50,0,0" TextWrapping="Wrap" Text="Scan" VerticalAlignment="Top" Height="20" Width="120" />
<TextBox Grid.Column="1" HorizontalAlignment="Left" Margin="200,50,0,0" TextWrapping="Wrap" Text="Scan" VerticalAlignment="Top" Height="20" Width="120" />
<TextBox Grid.Column="1" HorizontalAlignment="Left" Margin="350,50,0,0" TextWrapping="Wrap" Text="Scan" VerticalAlignment="Top" Height="20" Width="120" />
<!-- <ListView Grid.Row="0" Grid.Column="1" x:Name="curScans" Background="Aqua" Grid.ColumnSpan="1" Margin="1.8,0,-0.4,0">
<ListView.View>
<GridView>
<GridViewColumn Header="Scanner" DisplayMemberBinding="{Binding Path=curScanNum}" Width="150" />
<GridViewColumn Header="Operator" DisplayMemberBinding="{Binding Path=curOperator}" Width="200" />
<GridViewColumn Header="Task" DisplayMemberBinding="{Binding Path=curTask}" Width="200"/>
</GridView>
</ListView.View>
</ListView> -->
</Grid>
<ListView x:Name="pastScans" Background="#2FFFFFFF" DockPanel.Dock="Bottom">
<ListView.View>
<GridView>
<GridViewColumn Header="Scanner" DisplayMemberBinding="{Binding Path=ScannerNum}" Width="100" />
<GridViewColumn Header="Operator barcode" DisplayMemberBinding="{Binding Path=Operator}" Width="150" />
<GridViewColumn Header="MO/Task barcode" DisplayMemberBinding="{Binding Path=Task}" Width="150" />
<GridViewColumn Header="Date" DisplayMemberBinding="{Binding Path=ScanDate}" Width="100" />
<GridViewColumn Header="Time" DisplayMemberBinding="{Binding Path=ScanTime}" Width="100" />
<GridViewColumn Header="Quantity" DisplayMemberBinding="{Binding Path=Quantity}" Width="100" />
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ScanningV2
{
class scan
{
//Member variables
private string operatorCode;
public string OperatorCode
{
get { return operatorCode; }
set { operatorCode = value; }
}
private string taskCode;
public string TaskCode
{
get { return taskCode; }
set { taskCode = value; }
}
private int count;
public int Count
{
get { return count; }
set { count = value; }
}
private DateTime scanDateTime;
public DateTime ScanDateTime
{
get { return scanDateTime; }
set { scanDateTime = value; }
}
//Default Constructor
public scan()
{
operatorCode = null;
taskCode = null;
count = 0;
}
//Overload Constructor
public scan(string OperCode, string TaskMOCode, int CountNum)
{
operatorCode = OperCode;
taskCode = TaskMOCode;
count = CountNum;
}
}
}
You'll have to set an instance of your view-model class as the DataContext of your view. I usually do this in the code-behind of a view, so in your MainWindow.xaml.cs you would do the following:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new Scan();
}
}
Keep in mind that your view won't be able to detect changes unless you notify it. That's the point of the INotifyPropertyChanged interface:
class Scan : INotifyPropertyChanged
{
// Implementing the INotifyPropertyChanged interface:
public event PropertyChangedEventHandler PropertyChanged;
// A utility method to make raising the above event a little easier:
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
// Then, notify the view about changes whenever a property is set:
private string operatorCode;
public string OperatorCode
{
get { return operatorCode; }
set { operatorCode = value; RaisePropertyChanged("OperatorCode"); }
}
}
In your MainWindow.xaml, you can bind to that property:
<TextBlock Text="{Binding OperatorCode}" />
Now, whenever you set a new value for OperatorCode, your view will be notified so it can fetch and display the new value.
For ItemsSources, any IEnumerable will do - a List, an array... however, if you want the view to be notified whenever your collection changes, you'll have to use a class that implements INotifyCollectionChanged, such as ObservableCollection.
So, you create a bindable property in your view-model:
private ObservableCollection<string> names;
public ObservableCollection<string> Names
{
get { return names; }
set { names = value; RaisePropertyChanged("Names"); }
}
And you bind to that from within your view:
<ListView ItemsSource="{Binding Names}" />
Minor point: in C#, class names are usually written in CamelCase. Also, personally I prefer giving each view-model class a ViewModel postfix, so you can quickly see which classes are meant to be view-models. I try to match their name to the name of the view they belong to, so instead of 'scan', I would call this one 'MainWindowViewModel'.
You cannot bind any of that to any WPF UI elements because your code is too java-like.
You need to use Properties the C# way.
change all your get() and set() methods to real properties.
Related
So I'm currently trying out some different stuff, and I want to push the text from my textbox to a datagrid. I currently have made my XAML like this:
XAML:
<Grid>
<DataGrid HorizontalAlignment="Left" Height="160" Margin="138,54,0,0" VerticalAlignment="Top" Width="512">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}" Width="*" />
<DataGridTextColumn Header="NAME" Binding="{Binding NAME}" Width="*" />
</DataGrid.Columns>
</DataGrid>
<Label x:Name="lblID" Content="ID:" HorizontalAlignment="Left" Margin="138,239,0,0" VerticalAlignment="Top"/>
<Label x:Name="lblNAME" Content="NAME:" HorizontalAlignment="Left" Margin="138,265,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="txtID" HorizontalAlignment="Left" Height="23" Margin="187,243,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<TextBox x:Name="txtNAAM" HorizontalAlignment="Left" Height="23" Margin="188,271,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<Button x:Name="btnSend" Content="Send" HorizontalAlignment="Left" Margin="233,316,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
As you could see, I already have a button named send, and I've got 2 x a textbox named ID and NAME.
So how can I add the the text from the textboxes to the datagrid with only clicking on 1 button (SEND) and how do I keep repeating it for a second line, and a third line and so on?
if you have a View model then you can bind 2 different controls to the same field
this will means that when you change the text box it will use the binding to update the backing field and then the VM will notify all controls that they need to update the value
class VM : INofityPropertyChanged
{
private string text;
public string Text
{
get=>text;
set{
text=value;
PropertyChanged(this,new PropertyChangedEventArg("Text");
}
}
...
}
then use binding
<TextBox Text="{Binding Text}"/>
<Label Content="{Binding Text}"/>
this assumes you have set the VM as the data context
The primitive and straight forward way would be to simply add an event handler to the Button's Click event and add an instance of your class to the source collection of the DataGrid in there, e.g.:
public partial class MainWindow : Window
{
private readonly ObservableCollection<Model> _items = new ObservableCollection<Model>();
public MainWindow()
{
InitializeComponent();
dataGrid.ItemsSource = _items;
btnSend.Click += BtnSend_Click;
}
private void BtnSend_Click(object sender, RoutedEventArgs e)
{
_items.Add(new Model() { ID = txtID.Text, NAME = txtNAAM.Text });
}
}
public class Model
{
public string ID { get; set; }
public string NAME { get; set; }
}
If you are developing an enterprise application, you should look into refactoring your code to use a view model class in between the view (window) and the model for testability and separation of concerns reasons. This design pattern is known as MVVM (Model-View-ViewModel).
I am new to C# and WPF and still learning the ropes. I am currently trying use a ListBox to display some predefined items in a list. I am using an ObservableCollection to hold those items and I am binding that collection to that ListBox. I am also allowing the user to add new items to the list or update selected ones in addition to deleting them. For each item in that list I want to display a DELETE button beside it. However each button should only be visible for the items that have been added by the user and not any of the predefined items.
I am currently able to display the DELETE button for each item in the list. Therefore my question is, is it possible to set the the property of the DELETE button for each item in the list to be visible only for the items that were newly added to it and have no DELETE buttons showing for the predefined(default) items? If so, how would I go about doing that? (That is what I am struggling to figure out.)
Should I post my code?
Thanks
Here is the viewmode which has the list and the controls to add new items to the list.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListBox x:Name="DrinksListBox" HorizontalAlignment="Center" Height="325" Width="275" Margin="0,0,0,0" VerticalAlignment="Center" Grid.Column="0">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Type}" Width="80" Margin="0,0,10,0" Grid.Column="0"/>
<TextBlock Text="{Binding Name}" Width="80" Margin="0,0,10,0" Grid.Column="1" HorizontalAlignment="Left"/>
<Button x:Name="DrinkDeleteButton" Content="Delete" Click="CmdDeleteDrink_Clicked" HorizontalAlignment="Right" Grid.Column="2"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox x:Name="DrinkNameTextBox" Grid.Column="1" HorizontalAlignment="Left" Height="45" Margin="0,0,0,100" TextWrapping="Wrap" Text="Enter Drink Name" VerticalAlignment="Center" Width="240" FontSize="20" VerticalContentAlignment="Center"/>
<ComboBox x:Name="DrinkTypeComboBox" Grid.Column="1" HorizontalAlignment="Left" Margin="0,47,0,0" VerticalAlignment="Top" Width="240" Height="45" ItemsSource="{Binding Drinks, Mode=OneWay}" DisplayMemberPath="Type" FontSize="20"/>
<Button x:Name="AddDrinkButton" Content="Add Drink" Grid.Column="1" HorizontalAlignment="Right" Margin="0,0,10,100" VerticalAlignment="Center" Width="100" Height="45" Click="CmdAddDrink_Clicked"/>
</Grid>
Here is my code-behind. I have a inner class for the drink property and the main class that sets up the list to be used.
public partial class MainWindow : Window
{
public ObservableCollection<Drinks> Drinks { get; private set; }
public MainWindow()
{
InitializeComponent();
Drinks = new ObservableCollection<Drinks>();
Drinks.Add(new Drinks("Soda", "Pepsi"));
Drinks.Add(new Drinks("Tea", "Lemon"));
Drinks.Add(new Drinks("Caffinated", "Coffee"));
Drinks.Add(new Drinks("Other", "Water"));
DrinksListBox.ItemsSource = Drinks;
DrinkTypeComboBox.ItemsSource = Drinks;
}
private void CmdDeleteDrink_Clicked(object sender, RoutedEventArgs e)
{
Button cmd = (Button)sender;
if (cmd.DataContext is Drinks deleteDrink)
{
Drinks.Remove(deleteDrink);
}
}
private void CmdAddDrink_Clicked(object sender, RoutedEventArgs e)
{
string typeSelection = ((Drinks)DrinkTypeComboBox.SelectedItem).Type;
Drinks.Add(new Drinks(typeSelection, DrinkNameTextBox.Text));
}
}
Drink class has the type of drink and a name for it.
public class Drinks
{
private string type;
private string name;
public Drinks(string type, string name)
{
this.type = type;
this.name = name;
}
public string Type
{
get { return type; }
set
{
if (type != value)
{
type = value;
}
}
}
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
}
}
}
}
Let's say you have your item:
public class Drinks
{
//your properties, simplified for clarity
public string Name {get;set;}
public string Type {get;set;}
//hey, a new one!
public bool IsUserDefined {get;set;}
}
Then, when the user adds one:
private void CmdAddDrink_Clicked(object sender, RoutedEventArgs e)
{
string typeSelection = ((Drinks)DrinkTypeComboBox.SelectedItem).Type;
Drinks.Add(new Drinks(typeSelection, DrinkNameTextBox.Text)
{
IsUserDefined = true
});
}
disclaimer: from the top of my head; normally that means syntax errors; removed some parts for clarity.
<!-- In your resources section of the XAML -->
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<ListBox x:Name="DrinksListBox" ItemSource="{Binding Drinks}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Type}"/>
<TextBlock Text="{Binding Name}"/>
<Button x:Name="DrinkDeleteButton"
Visibility="{Binding Path=IsUserDefined,
Converter={StaticResource BoolToVis}}"/>
<!-- note: left out some attributes for clarity -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
That should do the trick.
Btw, you seem to be mixing some typical MVVM style coding and code-behind coding. It's worth to say that you might benefit by using a ViewModel in your code.
I'm pretty new to WPF (moving from WinForms). I'm trying to transfer some scenario from a WinForms application to a WPF one:
A window has a ListView control with 3 columns.
There is a button there to add new rows to that ListView.
The first and the second columns contain the ComboBox control.
The third column must contain different controls but just one at a time is visible. Which one is visible, it depends on the selected value of the ComboBox at the first column.
The content of the ComboBox at the second column changes every time a user selects a value from the ComboBox at the first column.
The general scenario is: a user selects a type from the list of types from the first ComboBox, after that the second ComboBox changes its content to a list of supported operations for the selected type and the third column at that time must change its content to display a control that supports the input for that type.
I know how to implement it using WinForms but I have no idea yet how to do it using WPF. Can someone help me to implement it or can anyone help with the information that facilitate implementing that?
I have the code so far:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
{
if (PropertyChanged != null) PropertyChanged(this, args);
}
}
public class RecordFilter : ViewModelBase
{
private static readonly ObservableCollection<KeyValuePair<PropertyInfo, string>> ColumnAliases =
new ObservableCollection<KeyValuePair<PropertyInfo, string>>(Card.ColumnAliases);
private KeyValuePair<PropertyInfo, string> _currentSelectedProperty;
public IEnumerable<OperationInfo> Operations
{
get
{
return Operations.GetOperationInfosForType(GetTypeUnwrapNullable(SelectedProperty.Key.PropertyType));
}
}
public OperationInfo SelectedOperation { get; set; }
public KeyValuePair<PropertyInfo, string> SelectedProperty
{
get { return _currentSelectedProperty; }
set
{
_currentSelectedProperty = value;
OnPropertyChanged("Operations");
}
}
public ObservableCollection<KeyValuePair<PropertyInfo, string>> Properties
{
get { return ColumnAliases; }
}
//DateTime or int or float, depends on the selected property type
//public object PropertyValue { get; set; }
}
Here is the XAML code:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Converters="clr-namespace:App.Converters" x:Class="App.DialogWindows.CardFilterWindow"
Title="Search filters" Height="347" Width="628" x:Name="wdw" ShowInTaskbar="False" WindowStartupLocation="CenterScreen">
<Window.Resources>
<Converters:NotNullObjectToEnabledConverter x:Key="NotNullObjectToEnabledConverter"/>
</Window.Resources>
<DockPanel>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Center" Height="Auto">
<Button x:Name="bnOK" Margin="5" Width="41" Content="OK" IsDefault="True" Click="bnOK_Click"/>
<Button x:Name="bnCancel" Margin="5" Content="Отмена" IsCancel="True"/>
</StackPanel>
<ListView ItemsSource="{Binding Filters, ElementName=wdw}" Name="LvExpr" DataContext="{Binding Filters, ElementName=wdw}">
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Header="Alias" Width="210">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox VerticalAlignment="Center"
ItemsSource="{Binding Properties}"
DisplayMemberPath="Value"
SelectedValue="{Binding SelectedProperty, Mode=TwoWay}"
/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Operation" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox VerticalAlignment="Center"
ItemsSource="{Binding Operations}"
DisplayMemberPath="OperationAlias"
SelectedValue="{Binding SelectedOperation, Mode=TwoWay}"
/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Value" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="ValidatesOnDataErrors=True}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="33">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Tag="{Binding Mode=OneWay}" Click="BnDelete_Click" ToolTip="Delete filter">
<Image Source="delete.ico" Height="16" Width="16"/>
</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
<GridViewColumnHeader>
<DataGridCell>
<Button Click="ButtonAdd_Click" Height="22" Padding="0" ToolTip="Add filter">
<Image Source="plus.ico" Focusable="False"/>
</Button>
</DataGridCell>
</GridViewColumnHeader>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</Window>
In your view-model, set up the list properties, and filter them out accordingly when the selected value changes (via the INotifyPropertyChanged.PropertyChanged event).
See this post for a comprehensive example. It uses a technique called MVVM that is used extensively with WPF and stands for ModelViewViewModel. I highly recommend you to learn this technique and utilize it in your XAML-related projects.
Here is one quick start tutorial, out of the many on the net.
I've been trying to practice with data binding and file IO concepts and for that I wrote this simple application that reads a 2 column, 5 row .csv file and displays the contents to a Listview in WPF with the feature that I can change the values of the 2nd column in my Listview (like a 2 way binding).
I have not been able to get any information to display in my window. I only get the column headers that I define in my MainWindow.xaml but none of the data binding is working.
Here is my code for the View Model and reading the file
namespace WpfPreview
{
public class LoadMovieData : BindableObject // My Data Context?
{
public string MovieName { get; set; }
private double year; public double Year { get { return year; } set { year = value; RaisePropertyChanged("Year"); } }
}
class ViewModel : BindableObject
{
private List<LoadMovieData> obsMovies = new List<LoadMovieData>();
public List<LoadMovieData> ObsMovies
{
get { return obsMovies; }
set { obsMovies = value; RaisePropertyChanged("ObsMovies"); }
}
public void ReadFile()
{
string filepath = System.IO.Path.Combine("C:\\Users\\Param\\Desktop", "excel.csv"); // Get filepath
using (var csvReader = new StreamReader(filepath)) // using this filepath
{
csvReader.ReadLine(); // read first line (headers)
csvReader.ReadLine(); // read first line of row data
while (!csvReader.EndOfStream) // while not end of file
{
var words = csvReader.ReadLine().Split(',').ToList(); // read line to list of columns
var x = new LoadMovieData() // new instance of data class
{
MovieName = words[0],
Year = Convert.ToDouble(words[1])
};
ObsMovies.Add(x); // add instance of data class to list variable
}
}
}
}
}
I'm not sure if my terms are correct. I am trying to follow the MVVM pattern. My codebehind for the window is this:
namespace WpfPreview
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
}
and here is my XAML part:
<Window x:Class="WpfPreview.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:WpfPreview"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border Background="White">
<ListView x:Name="MovieListView" ItemsSource="{Binding Path=ObsMovies}" VirtualizingStackPanel.IsVirtualizing="True" Background="Transparent">
<ListView.View>
<GridView>
<GridViewColumn Header="Movie Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ObsMovies.MovieName}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Year" Width="60">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding ObsMovies.Year}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Border>
</Grid>
I am very new to working with file IO and data binding/data context. I am sure there is an obvious mistake somewhere and that what I'm trying to do could be done in a much easier/less complicated way. Please feel free to give me suggestions to restructure my code.
I generally put my data loading code in my ViewModel constructor. Also, without an access modifier, your ViewModel class is private I believe, so you will not be able to call anything from outside the class. Consider making it public.
It looks like your obsMovies list should be an ObservableCollection. The value of obsMovies implements PropertyChanged notification, but if you add an item to it, the collection does not notify the UI that its collection has changed.
Change this:
private List<LoadMovieData> obsMovies = new List<LoadMovieData>();
public List<LoadMovieData> ObsMovies
{
get { return obsMovies; }
set { obsMovies = value; RaisePropertyChanged("ObsMovies"); }
}
To this:
private ObservableCollection<LoadMovieData> obsMovies = new ObservableCollection<LoadMovieData>();
public ObservableCollection<LoadMovieData> ObsMovies
{
get { return obsMovies; }
set { obsMovies = value; RaisePropertyChanged("ObsMovies"); }
}
You will have to import System.Collections.ObjectModel to make use of it.
Also, it looks like your bindings may not be quite right. Try using the following instead:
<ListView.View>
<GridView>
<GridViewColumn Header="Movie Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding ObsMovies.MovieName}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Year" Width="60">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding ObsMovies.Year}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
The above bindings omit the ObsMovies in the TextBlock bindings. Since each rows DataContext is one of the items in the collection, there is no need to have the collection referenced in the binding. Just have the binding path start at the datacontext level (in this case ObsMovies).
Lastly, as promised, a sample implementation of DataGrid:
<DataGrid HorizontalAlignment="Left" VerticalAlignment="Top" ItemsSource="{Binding ObsMovies}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Year}" ClipboardContentBinding="{x:Null}" Header="Year"/>
<DataGridTextColumn Binding="{Binding MovieName}" ClipboardContentBinding="{x:Null}" Header="Movie Name"/>
</DataGrid.Columns>
</DataGrid>
To have textboxes to allow for editing of items, in the listView example, replace the TextBlocks with TextBoxes, and for the DataGrid, specify a DataGridTemplateColumn and put a TextBox in the template:
<DataGridTemplateColumn ClipboardContentBinding="{x:Null}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Property}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I have got a Listview Item with a Gridview Child, bound to a List of Objects.
Below the Gridview I have got texboxes to edit the content of the Gridview (bound to the Gridview).
I can add new content (which is displayed in the GridView).
When i edit content, it is in fact edited (in the object list) but not displayed in the Gridview (the GridView does not seem to update)
xaml code:
<!-- ========= -->
<!-- root Grid -->
<!-- ========= -->
<Grid x:Name="root" Margin="10,10,10,10">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- ========= -->
<!-- Data Grid -->
<!-- ========= -->
<ListView x:Name="dataGrid" Grid.Row="0" Grid.ColumnSpan="2" ItemsSource="{Binding}">
<ListView.View>
<GridView>
<!-- first solution -->
<GridViewColumn x:Name="gridColumnName" Header="Name" Width="160">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentControl Content="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!-- second solution -->
<GridViewColumn x:Name="gridColumnPath" Header="Path" DisplayMemberBinding="{Binding Path=Path}" Width="490" />
</GridView>
</ListView.View>
</ListView>
<!-- ========= -->
<!-- Edit Menu -->
<!-- ========= -->
<Label Content="Name:" Grid.Row="1" Grid.Column="0" VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<TextBox x:Name="txtBoxName" Grid.Row="1" Grid.Column="1" Width="250" VerticalAlignment="Bottom" HorizontalAlignment="Left"
DataContext="{Binding ElementName=dataGrid, Path=SelectedItem}"
Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="Path:" Grid.Row="2" Grid.Column="0" VerticalAlignment="Bottom" HorizontalAlignment="Left" />
<TextBox x:Name="txtBoxPath" Grid.Row="2" Grid.Column="1" VerticalAlignment="Bottom" HorizontalAlignment="Stretch"
DataContext="{Binding ElementName=dataGrid, Path=SelectedItem}"
Text="{Binding Path=Path, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Object List class:
class ItemList : ObservableCollection<LdapItem>
{
public ItemList()
: base()
{
}
}
Object class:
class LdapItem : INotifyPropertyChanged
{
#region constructor
public LdapItem(String name, String path)
{
this.iD = Guid.NewGuid().ToString();
this.name = name;
this.path = path;
}
#endregion
#region public proterties
public String ID
{
get { return iD; }
}
public String Name
{
get { return name; }
set { name = value; }
}
public String Path
{
get { return path; }
set { path = value; }
}
#endregion
#region public methods
public void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
#endregion
#region private variables
private String name = String.Empty;
private String path = String.Empty;
private String iD = String.Empty;
#endregion
public event PropertyChangedEventHandler PropertyChanged;
}
any ideas why updating the GridView doesnt work?
If you have a number of models use a base class that implements INPC. Then change your property changed event handler to:
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
This will eliminate the need to specify the model property being changed. Reduces the number of misspelling errors or forgetting to put the name in. Still needs to call this.OnPropertyChanged() though which you are missing in several setters.
The ItemList class doesn't make sense. It could be replaced with:
public ObservableCollection<LdapItem> LdapItems
it seems like you forgot to fire the OnPropertyChangedEvent when your property changes:
public String Name
{
get { return name; }
set {
name = value;
OnPropertyChanged("Name");
}
}
If you don't fire the PropertyChanged event, WPF will not be able to see if the object has changed.