I did my research that people tend to use ViewModel to achieve this but I am sort of stuck in it.
I have a
public ObservableCollection<Order> orderList { get; set; } = new ObservableCollection<Order>();
in MainWindow which is already filled up with data.
in MainWindow XAML I have a User Control inside the TabControl:
<TabControl x:Name="TabCollection">
<TabItem Header="UC1">
<local:UserControl1/>
</TabItem>
<TabItem Header="UC2">
<local:UserControl2/>
</TabItem>
</TabControl>
We only talk about UC1 here so in UC1 XAML here I have a ListView inside:
<UserControl.DataContext>
<local:UserControl1VM/>
</UserControl.DataContext>
<ListView x:Name="ListViewText">
<ListView.View>
<GridView>
<GridViewColumn Header="First name" DisplayMemberBinding="{Binding Firstname}"/>
<GridViewColumn Header="Last Name" DisplayMemberBinding="{Binding Lastname}"/>
<GridViewColumn Header="Order" DisplayMemberBinding="{Binding Ordername}"/>
<GridViewColumn Header="Delivery time" DisplayMemberBinding="{Binding Deliverytime}"/>
<GridViewColumn Header="Phone Number" DisplayMemberBinding="{Binding Phone}"/>
<GridViewColumn Header="Address" DisplayMemberBinding="{Binding Address}"/>
<GridViewColumn Header="Email" DisplayMemberBinding="{Binding Email}"/>
</GridView>
</ListView.View>
</ListView>
And here's the code in UserControl1VM.cs:
namespace QuickShop
{
class UserControl1VM : INotifyPropertyChanged
{
private ObservableCollection<Order> orderList;
public ObservableCollection<Order> OrderList
{
get { return orderList; }
set
{
orderList = value;
PropertyChanged(this, new PropertyChangedEventArgs("OrderList"));
}
}
//
private void FindDeliveryOrders(IEnumerable<Order> sortList)
{
foreach (var order in sortList)
{
if (order.Delivery.Equals("Yes"))
{
//deliveryOrders.Add(order);
this.ListViewText.Items.Add(new Order { Firstname = order.Firstname, Lastname = order.Lastname, Ordername = order.Ordername, Deliverytime = order.Deliverytime, Phone = order.Phone, Address = order.Address, Email = order.Email });
}
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
}
And Of course these are incomplete codes because I don't know how to proceed next.
My goal is just to populate the ListView and it will automatically update itself if orderList changes. But right now I couldn't even know whether the ViewModel is working or not, any thoughts and code demo would be very grateful.
A UserControl should never have a "private" view model, as you assign it to the DataContext in the UserControl's XAML. It should instead expose dependency properties that could be bound to properties of an externally provided view model object.
Declare an ItemsSource property like this:
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
nameof(ItemsSource), typeof(IEnumerable), typeof(UserControl1));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public UserControl1()
{
InitializeComponent();
}
}
And bind the ListView like this:
<UserControl ...>
...
<ListView ItemsSource="{Binding ItemsSource,
RelativeSource={RelativeSource AncestorType=UserControl}}">
...
</ListView>
...
</UserControl>
When you use the UserControl, bind the property to a view model property:
<TabItem Header="UC1">
<local:UserControl1 ItemsSource="{Binding OrderList}"/>
</TabItem>
The last XAML snippet assumes that the object in the UserControl's DataContext has a OrderList property. This would automatically happen when the TabControl is bound to a collection of view model objects with that property.
Alternatively, let the elements in the UserControl's XAML directly bind to the properties of the object in the inherited DataContext.
<UserControl ...>
...
<ListView ItemsSource="{Binding OrderList}">
...
</ListView>
...
</UserControl>
Your control would not have to expose additional bindable properties, but it would only work with DataContext objects that actually provide the expected source properties.
Related
I am trying to get a Dictionary to bind to a ListView. Having not worked, I changed the Datatype to ObservableCollection> but still no joy. I know I'm missing something silly but....
The data is readonly, meaning that the UI will not update it, only the code behind.
The XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<ListView Grid.Column="1" Background="Orange" ItemsSource="{Binding MyItems}">
<ListView.View>
<GridView>
<GridViewColumn Header="Item" DisplayMemberBinding="{Binding Key}"/>
<GridViewColumn Header="Quantity" DisplayMemberBinding="{Binding Value}"/>
</GridView>
</ListView.View>
</ListView>
The DataObject:
public ObservableCollection<KeyValuePair<string, int>> MyItems{ get; set; }
And the assignment:
this.MyItems = new ObservableCollection<KeyValuePair<string, int>>(
PIData.GetNeededItems(itemName));
You should assign the MyItems property before InitializeComponent is called.
public MainWindow()
{
MyItems = new ObservableCollection<KeyValuePair<string, int>>(
PIData.GetNeededItems(itemName));
InitializeComponent();
}
If that is not possible, implement INotifyPropertyChanged:
public partial class MainWindow : Window, INotifyPropertyChanged
{
...
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<KeyValuePair<string, int>> myItems;
public ObservableCollection<KeyValuePair<string, int>> MyItems
{
get { return myItems; }
set
{
myItems = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(MyItems)));
}
}
}
I'm trying to figure out how to bind a property of a custom user control that is placed inside of the cell template of a list view control but it's not working. All of the DisplayMemberBinding fields are working as expected, and I'm getting the correct values, but inside of that custom control, nothing is updating.
WPF LIstView Control
<ListView Margin="10" x:Name="lvHistory">
<ListView.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Header="Database" Width="150" DisplayMemberBinding="{Binding ActiveBackup.Database.Name, Mode=TwoWay}" />
<GridViewColumn Header="Start Time" Width="130" DisplayMemberBinding="{Binding ActiveBackup.StartTime, Mode=TwoWay}" />
<GridViewColumn Header="Time Elapsed" Width="100" DisplayMemberBinding="{Binding ActiveBackup.TimeElapsed, Mode=TwoWay}" />
<GridViewColumn Header="P2" Width="100" DisplayMemberBinding="{Binding Progress, Mode=TwoWay}" />
<GridViewColumn x:Name="progressColumn" Header="Progress" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<local:cProgressBarSmall x:Name="pr1" Value="{Binding Progress, Mode=TwoWay}" Visibility="Visible" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Code-Behind in the cProgressBarSmall control.
public partial class cProgressBarSmall : UserControl
{
public ActiveBackup ActiveBackup { get; set; }
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(decimal), typeof(cProgressBarSmall));
private decimal _value;
public decimal Value
{
get
{
return (decimal) GetValue(ValueProperty);
}
set
{
_value = value;
SetValue(ValueProperty, value);
p1.Value = value.ToDoubleNotNull();
pLabel.Text = value.ToPercent(0);
if (value == 0)
{
p1.Visibility = Visibility.Hidden;
pLabel.Visibility = Visibility.Hidden;
}
else if (value.ToDoubleNotNull() >= p1.Maximum)
{
pLabel.Text = "Finished!";
pLabel.Foreground = new SolidColorBrush(Colors.Black);
}
}
}
}
}
I can't find a way to access the "pr1" because it's in a DataTemplate and therefore not directly accessible from the code-behind. Does binding not work through? The column before it (the "P2" column) is just at test column I put in just to make sure that the value is in fact updating and it is and that displays correctly, however the "progressColumn" always just shows the default value.
Is there anything special to data binding inside of a ListView.View > GridView > GridViewColumn > GridViewColumn.CellTemplate > DataTemplate hierarchy?
First, if you put a breakpoint in your setter you'll find that it's not hit by the binding. That's because the Binding is setting the dependency property, not the C# property. They're different. The C# property with get/set is an optional wrapper around the dependency property.
The correct way to do this is to have little or no code behind (code behind's not evil; you just don't need any for this one), but use a binding in the usercontrol xaml to update the UI. You can hide and show controls, and update label text, with style triggers in the usercontrol XAML. You don't need any code behind for this.
But here's the simplest way to adapt your existing code to something that works.
public decimal Value
{
get { return (decimal)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(decimal), typeof(cProgressBarSmall),
new PropertyMetadata(0m, Value_ChangedCallback));
// Has to be static
private static void Value_ChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((cProgressBarSmall)d).OnValueChanged();
}
private void OnValueChanged()
{
p1.Value = Value.ToDoubleNotNull();
pLabel.Text = Value.ToPercent(0);
if (Value == 0)
{
p1.Visibility = Visibility.Hidden;
pLabel.Visibility = Visibility.Hidden;
}
else if (Value.ToDoubleNotNull() >= p1.Maximum)
{
pLabel.Text = "Finished!";
pLabel.Foreground = new SolidColorBrush(Colors.Black);
}
}
I created an attached property for a GridView's GridViewColumn to hide/show the whole column.
<GridViewColumn att:Visibility.IsVisible="{Binding ...}">
This works as long the property the attached property binds to exists in my main view model (the datacontext of my gridView).
In one case, the visibility of the column depends on a property of my list item (an item inside the collection that is the itemsource of my gridview).
How do I bind here?
I can access the item properties inside the cell template of the gridviewcolumn but not in the gridviewcolumn itself.
What I'm trying to achieve is, that I can bind the attached property to a property of my list item.
Is there a way to do this?
Edit:
Let's say I have a class:
public class Item : INotifyPropertyChanged
{
private string m_Text;
public string Text
{
get
{
return m_Text;
}
set
{
m_Text= value;
RaisePropertyChanged("Text");
}
}
private bool m_IsVisible;
public bool IsVisible
{
get
{
return m_IsVisible;
}
set
{
m_IsVisible = value;
RaisePropertyChanged("IsVisible");
}
}
}
Now I have an ObservableCollection of this class as ItemsSource of my ListView/GridView.
public ObservableCollection<Item> Items;
In my xaml:
<ListView ItemsSource={Binding Items}>
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn att:Visibility.IsVisible="{Binding /*What goes here? Should bind to IsVisible of my Collection Item*/}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text={Binding Text"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView/>
The visibility of my gridviewcolumn should now depend on the value of "IsVisible" of my collection item. Is that event possible?
You can bind your attached property to the IsVisible property of your collection. I noticed that your xaml syntax is incorrect cause compilation errors. Your xaml should look like this.
<ListView ItemsSource="{Binding Items}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn att:Visibility.IsVisible="{Binding IsVisible}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
Also you don't need to implement INotifyPropertyChanged on your Item class. Your ObservableCollection already implements INotifyCollectionChanged to it already will update the UI. Your Item class should just like like this
public class Item
{
public string Text { get; set; }
public bool IsVisible { get; set; }
}
My wpf project follows the MVVM pattern. In my view-model I have an IList of obejcts which I take from database. Every object from this IList has a property which is List.
When I open the view for that view-model, I have an ItemsControl with this property:
ItemsSource="{Binding TheIListOfObjects}"
and the items in that ItemsControl are actually showing information from List.
So, while the user is on the view, an itemscontrol is shown. What I want to do is this: while on the same view, if the user clicks a button, the List is changed. How can I make the ItemsControl to refresh and show the new info?
All you need to have is ObservableCollection type for the property type.
Xaml
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<Button Content="Click" Click="Button_Click" />
<ListView ItemsSource="{Binding People}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Window>
Xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private ObservableCollection<Person> _people = new ObservableCollection<Person>();
public ObservableCollection<Person> People
{
get { return _people; }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
People.Add(new Person { Name = "A" });
}
}
public class Person
{
public string Name { get; set; }
}
To make sure that changes in a collection are notified to its bound controls, you must use ObservableCollection<> instead of IList<>
In WPF, once we have data bound a collection to the ItemsSource property of a collection control, we don't refresh the ItemsSource property, or interact with it in any other way. Instead, we work with the data bound property value, so for your example...:
ItemsSource="{Binding TheIListOfObjects}"
... you should manipulate the TheIListOfObjects collection:
TheIListOfObjects = GetNewCollectionItems();
If you have correctly implemented the INotifyPropertyChanged interface in your view model, then your view should update as expected.
Rather than declaring a tab for each of the ObservableCollections explicitly as in the first TabControl below I need them to generate dynamically as in the second TabControl and have the ItemsSource of the nested ListView set to each of the nested ObservableCollections.
In other words: Why aren't the ItemSource bindings of the nested ListViews in the second TabControl working? Is there a way to set the index of the nested ObservableCollection to the index of the containing ObservableCollection?
Or: How do I make the second dynamic TabControl and nested ListViews look like the first static TabControl and nested ListViews?
using System.Collections.ObjectModel;
using System.Windows;
namespace GridViewColumns2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel viewModel = new ViewModel();
viewModel.ThingCollections = new ObservableCollection<ThingCollection>();
viewModel.ThingCollections.Add(new ThingCollection { Name = "One" });
viewModel.ThingCollections[0].Things = new ObservableCollection<Thing>();
viewModel.ThingCollections[0].Things.Add(new Thing { Name = "One.One" });
viewModel.ThingCollections[0].Things.Add(new Thing { Name = "One.Two" });
viewModel.ThingCollections[0].Things.Add(new Thing { Name = "One.Three" });
viewModel.ThingCollections.Add(new ThingCollection { Name = "Two" });
viewModel.ThingCollections[1].Things = new ObservableCollection<Thing>();
viewModel.ThingCollections[1].Things.Add(new Thing { Name = "Two.One " });
viewModel.ThingCollections[1].Things.Add(new Thing { Name = "Two.Two" });
viewModel.ThingCollections[1].Things.Add(new Thing { Name = "Two.Three" });
DataContext = viewModel;
}
}
public class ViewModel
{
public ObservableCollection<ThingCollection> ThingCollections { get; set; }
}
public class ThingCollection
{
public string Name { get; set; }
public ObservableCollection<Thing> Things { get; set; }
}
public class Thing
{
public string Name { get; set; }
}
}
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TabControl>
<TabItem>
<TabItem.Header>One</TabItem.Header>
<TabItem.Content>
<ListView ItemsSource="{Binding Path=ThingCollections[0].Things}">
<ListView.View>
<GridView>
<GridViewColumn Width="120" Header="Name" DisplayMemberBinding="{Binding Name}" />
</GridView>
</ListView.View>
</ListView>
</TabItem.Content>
</TabItem>
<TabItem>
<TabItem.Header>Two</TabItem.Header>
<TabItem.Content>
<ListView ItemsSource="{Binding Path=ThingCollections[1].Things}">
<ListView.View>
<GridView>
<GridViewColumn Width="120" Header="Name" DisplayMemberBinding="{Binding Name}" />
</GridView>
</ListView.View>
</ListView>
</TabItem.Content>
</TabItem>
</TabControl>
<TabControl Grid.Column="1" ItemsSource="{Binding Path=ThingCollections}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding Path=ThingCollections[0].Things}">
<ListView.View>
<GridView>
<GridViewColumn Width="120" Header="Name" DisplayMemberBinding="{Binding Name}" />
</GridView>
</ListView.View>
</ListView>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
When you're in a DataTemplate, the DataContext is already that of the item (in your case, a ThingCollection class.
So instead of:
<ListView ItemsSource="{Binding Path=ThingCollections[0].Things}">
Use:
<ListView ItemsSource="{Binding Path=Things}">
To diagnose future binding errors, hit F5 (start debugging) and then take a look in the Output Window. All binding errors will be recorded there. In this case, it would've indicated that it cannot find a property named ThingCollections in the object ThingCollection.
Remove ThingCollections[0]. in BindingPath. The DataContext will be set for you to the right ThingCollection.
<ListView ItemsSource="{Binding Path=Things}">