Binding class to ListView data template - c#

I am trying to bind a ListView with a Model List. The xaml is as -
<ListView Name="lvProductBinding" HorizontalAlignment="Left" Height="434" Margin="10,144,0,0" VerticalAlignment="Top" Width="909">
<ListView.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding ProductNo}" HorizontalAlignment="Left" Margin="967,153,-912,0" VerticalAlignment="Top" Width="895" Height="224" IsExpanded="False">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Height="195" VerticalAlignment="Top" Width="895" Margin="0,0,-2,0">
<Label Content="{Binding ProductDescription}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="181" Height="27" />
<Label Content="{Binding VendorName}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="181" Height="27" />
</StackPanel>
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In mu xaml.cs I am doing this inside the constructor -
List<ProductDetailsModel> products;
products = new List<ProductDetailsModel>();
ProductDetailsModel objProductDetailsModel = new ProductDetailsModel();
objProductDetailsModel.VendorProductInventory = new VendorProductInventory() { ProductNo = "XS-3487", ProductDescription = "Perfume", VendorName = "JohnDoe" };
products.Add(objProductDetailsModel);
objProductDetailsModel = new ProductDetailsModel();
objProductDetailsModel.VendorProductInventory = new VendorProductInventory() { ProductNo = "TT-23487", ProductDescription = "Shoes", VendorName = "Richard Gere" };
products.Add(objProductDetailsModel);
objProductDetailsModel = new ProductDetailsModel();
objProductDetailsModel.VendorProductInventory = new VendorProductInventory() { ProductNo = "VFG-33487", ProductDescription = "Socks", VendorName = "Tom Cruise" };
products.Add(objProductDetailsModel);
lvProductBinding.ItemsSource = products;
where the ProductDetailsModel class is defined as -
public class ProductDetailsModel : INotifyPropertyChanged
{
public ProductDetailsModel()
{
}
private VendorProductInventory _vendorProductInventory;
public VendorProductInventory VendorProductInventory
{
get
{
return _vendorProductInventory;
}
set
{
if (_vendorProductInventory != value)
{
_vendorProductInventory = value;
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, e);
}
}
}
Can someone please advise as to what I am doing wrong here.
Eagerly waiting for a reply.
Thanks,
Saket

There are several things wrong with the code you posted.
First, as Sheridan noted, your implementation of INotifyPropertyChanged is wrong, automatic updates to the data in the ListView won't work until it is correct.
Second, all of the bindings listed in your XAML file don't have matching public properties in the ProductDetailsModel class. More than likely your ListView is being populated and all the bindings are failing (you should see exceptions for this in the VS output window at runtime). Your bindings, as written, should look like:
{Binding Path=VendorProductInventory.ProductNo}

Related

Databinding with TreeView in WPF

I am working with data binding and tree views and I am not able to get my TreeView to populate in my WPF. I think I am relatively close, just a small tweak somewhere, but I can't seem to find it.
Here's my Project class:
public class Project
{
public Project(string Name, bool isFolder, Project ParentFolder)
{
this.Name = Name;
this.isFolder = isFolder;
Children = new List<Project>();
if (ParentFolder == null)
{
Path = Name;
}
else
{
Path = ParentFolder.Path + " > " + Name;
}
}
public string Path { get; private set; }
public string Name { get; set; }
public bool isFolder { get; set; }
public List<Project> Children { get; set; }
public IEnumerable<Project> ChildFolders
{
get
{
return Children.Where(p => p.isFolder);
}
}
public object Icon
{
get
{
if (isFolder)
{
return 0; // return folder icon
}
else
{
return 1; // return project icon
}
}
}
public IEnumerable<Project> SearchRecursively(string SearchString)
{
return GetAllChildren.Where(p => p.Name.Contains(SearchString));
}
private List<Project> GetAllChildren
{
get
{
List<Project> allChildren = new List<Project>();
foreach(Project child in Children)
{
allChildren.AddRange(child.GetAllChildren);
}
return allChildren;
}
}
}
}
Here is my MaiWindow.xaml.cs class that I will be using to make test data:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.BuildData();
}
private void BuildData()
{
List<Project> parents = new List<Project>();
Project parentOne = new Project("Apple", true, null);
Project parentTwo = new Project("Samsung", true, null);
Project parentThree = new Project("Google", true, null);
parents.Add(parentOne); parents.Add(parentTwo); parents.Add(parentThree);
Project appleMacBook = new Project("Mac", false, parentOne);
Project appleIpad = new Project("iPad", false, parentOne);
Project appleiPhone = new Project("iPhone", false, parentOne);
Project samsungGalaxy = new Project("Galaxy", false, parentTwo);
Project samsungNote = new Project("Note", false, parentTwo);
Project googlePixel = new Project("Pixel", false, parentThree);
Project googleChromecast = new Project("Chromecast", false, parentThree);
parents[0].Children.Add(appleMacBook); parents[0].Children.Add(appleIpad); parents[0].Children.Add(appleiPhone);
parents[1].Children.Add(samsungGalaxy); parents[1].Children.Add(samsungNote);
parents[2].Children.Add(googlePixel); parents[2].Children.Add(googleChromecast);
}
}
}
And here is my XAML where I am trying to display the TreeView. Right now, it is just blank. I would appreciate any tips.
<TreeView x:Name="Hierarchy" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="0,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="265"
ItemsSource="{Binding parents}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding parents}" DataType="{x:Type self:Project}">
<TreeViewItem Header="{Binding Name}"></TreeViewItem>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
Edit:
Here's the Property class:
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
private string name { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildFolders}">
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding Icon}" Margin="5, 5, 5, 5"></Image>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" BorderThickness="0" FontSize="16" Margin="5"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
So, this doesn't seem to be firing the change event. I know this because Path is set as Name + ">". When I change the Name, Path is not reflecting the change. It only shows what my previous value for Name was, if that makes sense.
if (ParentFolder == null)
{
Path = Name;
}
else
{
Path = ParentFolder.Path + " > " + Name;
}
Edit:
public Project(string Name, bool isFolder, Project ParentFolder)
{
this.Name = Name;
this.isFolder = isFolder;
Children = new List<Project>();
this.ParentFolder = ParentFolder;
}
public string Path
{
get
{
return this.ParentFolder + " > " + this.Name;
}
set
{
this.Path = Path;
}
}
XAML:
<TextBox x:Name="FolderNameBox" Grid.Column="1" Background="White" Grid.Row="1" Grid.ColumnSpan="5"
Margin="0,0,287,654.333" VerticalContentAlignment="Center"
Padding="6" FontSize="16"
IsReadOnly="True"
Text="{Binding ElementName=Hierarchy, Path=SelectedItem.Path, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
<TextBox x:Name="SearchProjectsBox" Grid.Column="5" Background="White" Grid.Row="1" Text="Search Projects"
Margin="47.333,0,0,654.333" VerticalContentAlignment="Center" Foreground="LightGray" Padding="6" FontSize="16" HorizontalAlignment="Left" Width="268" GotFocus="TextBox_GotFocus" LostFocus="TextBox_LostFocus"/>
<TreeView x:Name="Hierarchy" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="0,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="226"
ItemsSource="{Binding Projects}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildFolders}">
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding Icon}" Margin="5, 5, 5, 5"></Image>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" BorderThickness="0" FontSize="16" Margin="5"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<Grid Grid.ColumnSpan="2" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="245,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="540">
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
<ListView Margin="0,0,10,0" Name="ProjectView" ItemsSource="{Binding Projects}" FontSize="16" Foreground="Black">
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource GridHeader}">
<GridViewColumn Header="Name" Width="200" DisplayMemberBinding="{Binding ElementName=Hierarchy, Path=SelectedItem.Name, UpdateSourceTrigger=PropertyChanged}"></GridViewColumn>
<GridViewColumn Header="Directory" Width="328" DisplayMemberBinding="{Binding ElementName=Hierarchy, Path=SelectedItem.Path, UpdateSourceTrigger=PropertyChanged}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</ScrollViewer>
</Grid>
</Grid>
The Path updates too but when it I see it it will display the path of the project rather than the fired change of name. It changes in real-time but doesn't save the String value..only registers that a change has happened.
Heres my Property Change too.
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You have a few problems here.
ItemsSource="{Binding parents}"
Here's parents:
private void BuildData()
{
List<Project> parents = new List<Project>();
You're asking XAML to examine all the methods in the codebehind class, looking for local variables named parents. This isn't a reasonable request.
There are a few requirements if you want to bind to parents: It must be...
A public...
Property (not a field -- it needs a get block)...
Of whatever object is your TreeView's DataContext.
None of those are true.
Two more things -- not required, but a very good idea:
Make it ObservableCollection<T> rather than List<T>, so that it will notify the UI of added or removed items.
The class that owns it should be a viewmodel class, not your window/usercontrol. When we say "viewmodel", we mean it implements INotifyPropertyChanged and raises PropertyChanged when its property values change. Again, this is about keeping the UI informed of changes.
Keeping the UI informed is what bindings are all about: They listen for changes in the viewmodel and update the UI.
So you need a main viewmodel that looks like this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// C#6
/*
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
*/
protected virtual void OnPropertyChanged(string propName)
{
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propName));
}
}
}
public class MainViewModel : ViewModelBase
{
private ObservableCollection<Project> _projects;
public ObservableCollection<Project> Projects {
get { return _projects; }
set {
if (value != _projects) {
_projects = value;
OnPropertyChanged(nameof(Projects));
}
}
}
public void BuildData() {
Projects = new ObservableCollection<Project>();
// do the rest of the stuff
}
}
And you should rewrite your Project class as a ProjectViewModel derived from ViewModelBase, make it raise PropertyChanged in the same way, and use ObservableCollection<Project> for Children.
And in your main window...
public MainWindow()
{
InitializeComponent();
var vm = new MainViewModel();
vm.BuildData();
DataContext = vm;
}
Your XAML needs a little work, too.
Projects has a capitalized name now
For the item template, you are binding to the property of the child item which provides the tree view item's children. That's the Children property of your Project class.
A datatemplate tells XAML how to present the content of a control. The tree creates a TreeViewItem with a Project as its DataContext, and then uses your HierarchicalDataTemplate to turn that DataContext into some kind of visual content. You don't use the template to create a TreeViewItem; you use it to create the visual stuff in the TreeViewItem.
So here's the new XAML:
<TreeView
x:Name="Hierarchy"
ItemsSource="{Binding Projects}"
Grid.Column="4"
HorizontalAlignment="Left"
Height="631"
Margin="0,58,0,0"
Grid.Row="1"
VerticalAlignment="Top"
Width="265"
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Label Content="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
There's no reason to get in the habit of DataContext = this;. Once you start that, the next thing you know you'll be doing it in a UserControl and coming here asking why all your bindings to it in the parent XAML are broken. Dependency properties are a bigger hassle than INPC, and you end up with code that ought to be in a viewmodel mixed into your MainWindow code. If you use viewmodelsit's the easiest thing in the world to shuffle your UI around. Maybe you want the original content of your main window to be just one of three tab pages in the main window. Keeping code separated properly makes that kind of thing much simpler.

Binding issues with label but not listbox

So I have done binding before and I'm not sure what is wrong with my binding this time. Can someone please explain to me why I can't bind my labels to the appropriate properties? The listbox works perfectly and as expected but I don't know why the labels aren't working.
XAML
<Label Content="{Binding MetricName}" Height="25"></Label>
<Label Content="{Binding MetricUnit}" Height="25"></Label>
<ListBox x:Name="EconomicSelection" Grid.Column="1" HorizontalAlignment="Left" Height="150" Margin="5,5,0,5" VerticalAlignment="Top" Width="399" FontFamily="{DynamicResource FontFamily}" FontSize="11" ItemsSource="{Binding EconomicBenchmarks}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="P" Grid.Column="0"/>
<TextBox Text="{Binding Percentile}" Grid.Column="0" Width="30"/>
<Label Content="Value: " Grid.Column="1"/>
<TextBox Text="{Binding Value}" Grid.Column="1"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
C#
private Metric economicMetric;
public Metric EconomicMetric
{
get { return economicMetric; }
set
{
if (value == economicMetric) return;
economicMetric = value;
OnPropertyChanged();
}
}
private string metricName;
public string MetricName
{
get { return metricName; }
set
{
if (value == metricName) return;
metricName = value;
OnPropertyChanged();
}
}
private string metricUnit;
public string MetricUnit
{
get { return metricUnit; }
set
{
if (value == metricUnit) return;
metricUnit = value;
OnPropertyChanged();
}
}
private ObservableCollection<EconomicBenchmark> economicBenchmarks = new ObservableCollection<EconomicBenchmark>();
public ObservableCollection<EconomicBenchmark> EconomicBenchmarks
{
get { return economicBenchmarks; }
}
public EconomicMeasuresTable()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
public event EconomicMeasuresChangedEventHandler EconomicMeasuresChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (propertyName == "EconomicMetric")
{
metricName = economicMetric.EconomicName;
metricUnit = economicMetric.Unit;
economicBenchmarks.Clear();
foreach (var economicBenchmark in economicMetric.EconomicBenchmarks)
{
economicBenchmarks.Add(economicBenchmark);
}
}
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private void SetButton_Click(object sender, RoutedEventArgs e)
{
if (EconomicMeasuresChanged != null)
{
EconomicMeasuresChanged(this, new EventArgs());
}
}
}
public delegate void EconomicMeasuresChangedEventHandler(object sender, EventArgs e);
List
List<Metric> metrics = new List<Metric>();
metrics.Add(new Metric { EconomicItem = "NVP", EconomicName = "Net Present Value", EconomicBenchmarks = GetEconomicBenchmarks(new[] { 10, 50, 90 }, new[] { 400, 550, 700 }), Unit = "$m" });
metrics.Add(new Metric { EconomicItem = "ROI", EconomicName = "Return On Investment", EconomicBenchmarks = GetEconomicBenchmarks(new[] {10, 50, 90}, new[] {10, 20, 30}), Unit = "%" });
metrics.Add(new Metric { EconomicItem = "STOIIP", EconomicName = "STOIIP", EconomicBenchmarks = GetEconomicBenchmarks(new[] {10, 50, 90}, new[] {500, 550, 600}), Unit = "MMbbl" });
What you are doing in this code is maybe incomplete and a bit strange. First of all, i would never use OnPropertyChanged to set any value, but maybe thats only my opinion...
Anyway, in the code you have shown us,you set metricName and metricUnit in OnPropertyChanged if property is EconomicMetric. But you never set EconomicMetric anywhere so the binding properties would never be set. You need to have a better look at your code.
What I understand from your code is Metric class contains the collection EconomicBenchmarks, then why don't you bind directly that collection with view like this,
<ListBox x:Name="EconomicSelection" Grid.Column="1" HorizontalAlignment="Left" Height="150" Margin="5,5,0,5" VerticalAlignment="Top" Width="399" FontSize="11" ItemsSource="{Binding EconomicMetric.EconomicBenchmarks}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="P" Grid.Column="0"/>
<TextBox Text="{Binding Percentile}" Grid.Column="0" Width="30"/>
<Label Content="Value: " Grid.Column="1"/>
<TextBox Text="{Binding Value}" Grid.Column="1"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Best approach to add an item to a databound Listbox using MVVM pattern?

I'm facing a problem in my WPF project at the moment. At this moment I have a Viewmodel which has a Manager (to communicate with the repo).
internal class TicketViewModel
{
private TicketManager mgr;
public IEnumerable<Ticket> List { get; set; }
public TicketViewModel()
{
mgr = new TicketManager();
List = mgr.GetTickets();
}
}
I've managed to bind this list to the Listbox in my MainWindow. The next step is that I need to add an extra ticket to the list and also pass this through the manager. The problem is I need two parameters from some Controls in the MainWindow. From MVVM perspective I need to use bound Commands on e.g. a Button to communicate with the viewmodel as my viewmodel can't/may not access controls from the window. Is using parameterized Commands the way to go here?
The next problem is that the Listbox won't update I guess. This is the code:
<ListBox x:Name="listboxTix" BorderThickness="0" ItemsSource="{Binding List}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" Background="Beige" BorderThickness="2">
<StackPanel Width="250">
<TextBlock Text="{Binding TicketNumber}" />
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding State}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I found that using a CompareableCollection is the way to go here, but then I still have to read all the Tickets again after adding a new Ticket.
Thanks in advance,
Hicy
okay here is the code.
Lets say you have three textboxes on MainWindow(since you have three Textblocks.) so Your MainWindow.xaml looks like
<Window.DataContext>
<local:MyViewModel/>--set's your viewModel
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="250*"/>
<RowDefinition Height="90"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" x:Name="listboxTix" BorderThickness="0" ItemsSource="{Binding List}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" Background="Beige" BorderThickness="2">
<StackPanel Width="250">
<TextBlock Text="{Binding TicketNumber}" />
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding State}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox x:Name="TicketNumber" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=Text}" VerticalAlignment="Top" Width="120"/>
<TextBox x:Name="Text" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=State}" />
<TextBox x:Name="State" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=TicketNumber}" />
<Button Content="Button" Command="{Binding Path=MainCommand}" Grid.Row="2"/>
</Grid>
and I am assuming that you have some class called class Ticket which contain these three members
Class Ticket
{
public int TicketNumber { get; set; }
public string Text { get; set; }
public string State { get; set; }
}
Now in class TicketManager we fill it with some dummy data
class TicketManager
{
ObservableCollection<Ticket> tl = new ObservableCollection<Ticket>();
internal ObservableCollection<Ticket> GetTickets()
{
tl.Add(new Ticket() { State = "State1", Text = "Text1", TicketNumber = 1 });
tl.Add(new Ticket() { State = "State2", Text = "Text2", TicketNumber = 2 });
tl.Add(new Ticket() { State = "State3", Text = "Text3", TicketNumber = 3 });
return tl;
}
}
and in your Mainwindow ViewModel lets call it MyViewModel.cs we add
class MyViewModel:INotifyPropertyChanged
{
private TicketManager mgr;
public ObservableCollection<Ticket> List { get; set; }
private string text;
private string state;
private int ticketNumber;
private readonly DelegateCommand<object> MyButtonCommand;
public Class1()
{
mgr = new TicketManager();
List = mgr.GetTickets();
MyButtonCommand = new DelegateCommand<object>((s) => { AddListToGrid(text, state, ticketNumber); }, (s) => { return !string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(state); });
}
private void AddListToGrid(string text, string state, int ticketNumber)
{
List.Add(new Ticket() {Text=text,State=state,TicketNumber=ticketNumber });
}
public DelegateCommand<object> MainCommand
{
get
{
return MyButtonCommand;
}
}
public string Text
{
get
{
return text;
}
set
{
text = value;
OnPropertyChanged("Text");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
public string State
{
get
{
return state;
}
set
{
state = value;
OnPropertyChanged("State");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
public int TicketNumber
{
get
{
return ticketNumber;
}
set
{
ticketNumber = value;
OnPropertyChanged("TicketNumber");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
public event PropertyChangedEventHandler PropertyChanged;
}
You can Modify the code in anyway you want
This ViewModel implements fewthings which are very important from MVVM point of view
1) INotifyPropertyChanged
2) WPF Delegate Command
P.S:The code is tested and it runs as expected
Don't get hung up on MVVM it is simply a separation of data from a view, and models are shared between the two with a majority of the business logic (on a shared component) should be performed on the VM; it is not a religion just a three tiered data system. IMHO
If your button needs to do an operation, have it make a call, most likely in the code behind, to a method on the VM which handles the business logic, updates the list with the new item and notifies the manager.
I would bind the list in question to an ObservableCollection which can notify upon insert/delete of an item.

Combobox not showing the items

Here is my ViewModel Class
namespace ERP_Lite_Trial.ViewModels
{
public class GroupsViewModel : INotifyPropertyChanged
{
public GroupsViewModel()
{
using (DBEntities db = new DBEntities())
{
var groups = (from g in db.Groups
select g.Name).ToList();
this.GroupName = groups;
var correspondingEffects = (from g in db.Groups
select g.Type_Effect.Name).ToList();
this.EffectCorrespondingToGroup = correspondingEffects;
}
}
private List<string> _groupName;
public List<string> GroupName
{
get
{
return _groupName;
}
set
{
_groupName = value;
OnPropertyChanged("GroupName");
}
}
private List<string> _effectCorrespondingToGroup;
public List<string> EffectCorrespondingToGroup
{
get
{
return _effectCorrespondingToGroup;
}
set
{
_effectCorrespondingToGroup = value;
OnPropertyChanged("EffectCorrespondingToGroup");
}
}
public void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Now I will show you the two cases :
Case 1 : Works Well
<ComboBox x:Name="cbUnder" ItemsSource="{Binding Path=GroupName}" IsEditable="True"
Grid.Column="1" Grid.ColumnSpan="4" Grid.Row="3" />
In the above case I get all the group names from my database and are displayed correctly as items of the comboBox. But this is not what I want. I want to display two columns in this combobox.
Case 2: Not working as expected (There might be some silly mistake done by me)
<ComboBox x:Name="cbUnder" IsEditable="True" Grid.Column="1" Grid.ColumnSpan="4" Grid.Row="3">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=GroupName}" Width="100"/>
<TextBlock Text="|" Width="10" />
<TextBlock Text="{Binding Path=EffectCorrespondingToGroup}" Width="100"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I did not get any errors in this case but my combobox is not showing any Items.
Your code needs to create the information you currently have in two lists as they relate to each other in one list. As two separate lists, there is no way to relate them to one another.
First change your DB query to return the information as a list of objects.
using (DBEntities db = new DBEntities())
{
GroupsAndEffects = (from g in db.Groups
select new GroupAndEffect
{
GroupName = g.Name
EffectCorrespondingToGroup = g.Type_Effect.Name
}).ToList();
}
the var groups needs to be a list of objects, instead of a list of strings:
private List<GroupAndEffect> _groupAndEffects;
public List<GroupAndEffect> GroupsAndEffects
{
get
{
return _groupAndEffects;
}
set
{
_groupAndEffects = value;
OnPropertyChanged("GroupsAndEffects");
}
}
Requiring a GroupAndEffect Class
public class GroupAndEffect
{
public string GroupName;
public string EffectCorrespondingToGroup;
}
Update Case 2:
<ComboBox x:Name="cbUnder" ItemsSource="{Binding GroupsAndEffects}" IsEditable="True" Grid.Column="1" Grid.ColumnSpan="4" Grid.Row="3">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding GroupName}"/>
<TextBlock Text="|" Width="10" />
<TextBlock Text="{Binding EffectCorrespondingToGroup}" Grid.Column="1" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate></ComboBox>

How can I bind Foreground to a property in my ViewModel?

I would like to bind the foreground property of a TextBlock to a Property in my ViewModel.
This doesn't work :
Edit
View :
TextBlock
Text="{Binding Path=FullName, Mode=OneWay}"
Foreground="{Binding Path=ForegroundColor}"
Margin="0 5 3 5"
Code behind:
CustomerHeaderViewModel customerHeaderViewModel = new CustomerHeaderViewModel();
customerHeaderViewModel.LoadCustomers();
CustomerHeaderView.DataContext = customerHeaderViewModel;
View Model:
private System.Windows.Media.Brush _foregroundColor;
_foregroundColor = System.Windows.Media.Brushes.DarkSeaGreen;
public System.Windows.Media.Brush ForegroundColor
{
get { return _foregroundColor; }
set { _foregroundColor = value;
OnPropertyChanged("ForegroundColor");
}
}
public CustomerHeaderViewModel()
{
ForegroundColor = System.Windows.Media.Brushes.Red;
}
All other properties (Text etc) correctly bind.
What am I doing wrong?
Check if your solution is like that:
View:
<Window x:Class="WpfApplication13.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:vm="clr-namespace:WpfApplication13"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainVM/>
</Window.DataContext>
<Grid>
<TextBlock Text="{Binding Path=FullName, Mode=OneWay}"
Foreground="{Binding Path=ForegroundColor}"
Margin="0 5 3 5"/>
</Grid>
</Window>
ViewModel:
public class MainVM : INotifyPropertyChanged
{
protected void OnPropertyChanged(string porpName)
{
var temp = PropertyChanged;
if (temp != null)
temp(this, new PropertyChangedEventArgs(porpName));
}
public event PropertyChangedEventHandler PropertyChanged;
private System.Windows.Media.Brush _foregroundColor = System.Windows.Media.Brushes.DarkSeaGreen;
public string FullName
{
get
{
return "Hello world";
}
}
public System.Windows.Media.Brush ForegroundColor
{
get { return _foregroundColor; }
set
{
_foregroundColor = value;
OnPropertyChanged("ForegroundColor");
}
}
}
and remember that if you want to set new value for ForegroundColor in VM you sholud do it like that:
ForegroundColor = System.Windows.Media.Brushes.Red;
to raise PropertyChangedEvent
Accordind to new information about your problem, you could try this solution:
CustomerHeaderViewModel.cs
class CustomerHeaderViewModel : INotifyPropertyChanged
{
public ObservableCollection<Customer> Customers { get; set; }
public void LoadCustomers()
{
ObservableCollection<Customer> customers = new ObservableCollection<Customer>();
//this is where you would actually call your service
customers.Add(new Customer { FirstName = "Jim", LastName = "Smith", NumberOfContracts = 23 });
customers.Add(new Customer { FirstName = "Jane", LastName = "Smith", NumberOfContracts = 22 });
customers.Add(new Customer { FirstName = "John", LastName = "Tester", NumberOfContracts = 33 });
customers.Add(new Customer { FirstName = "Robert", LastName = "Smith", NumberOfContracts = 2 });
customers.Add(new Customer { FirstName = "Hank", LastName = "Jobs", NumberOfContracts = 5 });
Customers = customers;
}
protected void OnPropertyChanged(string porpName)
{
var temp = PropertyChanged;
if (temp != null)
temp(this, new PropertyChangedEventArgs(porpName));
}
public event PropertyChangedEventHandler PropertyChanged;
private System.Windows.Media.Brush _foregroundColor = System.Windows.Media.Brushes.DarkSeaGreen;
public System.Windows.Media.Brush ForegroundColor
{
get { return _foregroundColor; }
set
{
_foregroundColor = value;
OnPropertyChanged("ForegroundColor");
}
}
}
CustomerHeaderView.xaml
<UserControl x:Class="TestMvvm444.Views.CustomerHeaderView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="main">
<Grid>
<StackPanel HorizontalAlignment="Left">
<ItemsControl ItemsSource="{Binding Path=Customers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal">
<TextBox
Text="{Binding Path=FirstName, Mode=TwoWay}"
Width="100"
Margin="3 5 3 5"/>
<TextBox
Text="{Binding Path=LastName, Mode=TwoWay}"
Width="100"
Margin="0 5 3 5"/>
<TextBlock
Text="{Binding Path=FullName, Mode=OneWay}"
Foreground="{Binding ElementName=main, Path=DataContext.ForegroundColor}"
Margin="0 5 3 5"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
In presented scenario the ForegroundColor property resides in CustomerHeaderViewModel.cs so it is value for all customers. In CustomerHeaderView.xaml I added x:Name for UserControl to have a possiblity to refer to DataContext of this element. If you don't want to use x:Name for UserControl, you can try this:
<TextBlock
Text="{Binding Path=FullName, Mode=OneWay}"
Foreground="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}, Path=DataContext.ForegroundColor}"
Margin="0 5 3 5"/>
Remember that DataContext of this control was set earlier in MainWindow.cs.
MainWindow.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
CustomerHeaderViewModel customerHeaderViewModel = new CustomerHeaderViewModel();
customerHeaderViewModel.LoadCustomers();
CustomerHeaderView.DataContext = customerHeaderViewModel;
}
}
This is not a good practice to put UI elements in your view model. Your view model must only encapsulate business locig.
If you want to change the color of anything in your UI that depends on on the value of your textbox, it's a better practice to use data triggers in XAML.
You can do like this :
Viewmodel :
public class MainVm : INotifyPropertyChanged
{
protected void OnPropertyChanged(string porpName)
{
var temp = PropertyChanged;
if (temp != null)
temp(this, new PropertyChangedEventArgs(porpName));
}
public event PropertyChangedEventHandler PropertyChanged;
public string FullName
{
get { return "Hello world"; }
}
}
XAML (Edited to use the color picker, assuming the selected value of his control is named "SelectedValue" and that it returns a Brush object)
<Grid>
<TextBlock Text="{Binding Path=FullName, Mode=OneWay}"
Margin="0 5 3 5" Foreground="{Binding ElementName=colorpicker, Path=SelectedValue}"/>
<ColorPicker x:Name="colorpicker"/>
</Grid>

Categories

Resources