I am trying to create a button which has the text "User Requests" in a list view, and once I click on it I would like it to filter some specific elements from a different list.
The code I have so far does not even display the button in the view, let alone have the desired functionality.
This is what I have so far. Any help will be very much appreciated.
<ListView
ItemsSource="{Binding UserRequests}"
SelectionMode="Single"
Width="Auto"
ItemContainerStyle="{StaticResource listViewSingleClick}"
mvvm:CommandBehavior.Command="{Binding ViewOrder}"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Hidden">
<ListView.View>
<GridView>
<GridViewColumn Header="Custom Filters" Width="170">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text=" {Binding UserRequests}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Like the comment by sk_ said, there are numerous possible issues with the code you presented, such as possibly faulty data binding, and not actually having a button control.
I'll just present you with some sample code, hopefully this will enable you to add the desired functionality to your own code. Please keep in mind that this is just how I'd satisfy the requirement and this is not the only way to to this.
ViewModel(s):
public class UserRequest : BaseViewModel {
public String Text { get; set; }
public ICommand Command { get; set; }
public UserRequest()
{
Command = new RelayCommand(ActionToExecute);
}
public void ActionToExecute()
{
//Doing stuff here!
}
}
public class SomeViewModel : BaseViewModel
{
public ObservableCollection<UserRequest> UserRequests { get; set; }
public SomeViewModel()
{
UserRequests = new ObservableCollection<UserRequest>();
UserRequests.Add(new UserRequest() {Text = "Test1"});
UserRequests.Add(new UserRequest() { Text = "Test2" });
}
}
BaseViewModel just implements INotifyPropertychanged. RelayCommand is a helper class that can be found here. We need the Command Property in order to bind an action to the button click. In the constructor to SomeViewModel we just add some items to our list (so we actually see something in the View).
MainWindow.cs:
public partial class MainWindow : Window {
public SomeViewModel SomeViewModel { get; set; }
public MainWindow() {
SomeViewModel = new SomeViewModel();
DataContext = SomeViewModel;
InitializeComponent();
}
}
For simplicity I set the DataContext in the MainWindows code-behind. Feel free to set the data context some different way if you prefer.
MainWindow.xaml:
<Grid>
<ListView
ItemsSource="{Binding UserRequests}"
SelectionMode="Single"
Width="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Hidden">
<ListView.View>
<GridView>
<GridViewColumn Header="Custom Filters" Width="170">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding Command}" Content="{Binding Text}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
Here I removed some lines (such as ViewOrder) for simplicity. This will result in a list view containing buttons (with the text defined in SomeViewModel). Clicking on one of those buttons will result in the ActionToExecute method being executed.
Related
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.
As someone who has recently switched from WinForms to WPF, I'm still struggling and going nuts trying to figure out a way to loop through and delete checked ListView items.
This method gives me error: "ListView does not contain a definition for CheckedItems..."
if (lvFilesList != null)
{
foreach (ListViewItem lvItem in lvFilesList.CheckedItems)
{
lvItem.Checked = False;
}
}
My XAML Code:
<ListView Height="400" Width="400"
Name="lvFilesList"
ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="chk" IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></CheckBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}"/>
<GridViewColumn Header="File" DisplayMemberBinding="{Binding File}"/>
<GridViewColumn Header="Author" DisplayMemberBinding="{Binding Author}"/>
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock FontWeight="Bold" Text="Group"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
It looks odd that the ItemsSource of your ListView is directly bound to the current DataContext by
ItemsSource="{Binding}"
The DataContext would usually hold a view model object with a collection-type property like
public class Item
{
public bool IsChecked { get; set; }
// other properties like ID, File, Author
}
public class ViewModel
{
public ObservableCollection<Item> Items { get; }
= new ObservableCollection<Item>();
}
and the Binding would be
ItemsSource="{Binding Items}"
Then the view model could have a method that deletes all checked items, like
public void DeleteCheckedItems()
{
var checkedItems = Items.Where(item => item.IsChecked).ToList();
checkedItems.ForEach(item => Items.Remove(item));
}
Note that you usually assign an instance of the view model class to the DataContext of your main view, e.g. in the MainWindow constructor:
private readonly ViewModel viewModel = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = viewModel;
viewModel.Items.Add(new Item { ID = 1, ... });
viewModel.Items.Add(new Item { ID = 2, ... });
}
for some time I am struggling with ObservableCollection containing List. I am not able to get it working in a way, that each List is in its own expander.
You can imagine input data as a list of records per day and each list contains orders for that day.
Here is some code:
My version 1:
public class Order
{
public string OrderId { get; private set}
}
public ObservableCollection<Order> ObservableResults
{
get
{
return new ObservableCollection<Order>
{
new Order("Foo"),
new Order("Bar")
};
}
}
XAML file:
<ScrollViewer>
<ListView Background="Transparent" ItemsSource="{Binding ObservableResults}">
<ListView.View>
<GridView>
<GridViewColumn Header="Order Id" Width="650" DisplayMemberBinding="{Binding OrderId, UpdateSourceTrigger=PropertyChanged}" />
</GridView>
</ListView.View>
/ListView>
</ScrollViewer>
This one worked just fine, but the problem was, that I had all the orders in a single grid view and that's something I did not really like so I wanted to modify it, that I will get List of orders and fill the ObservableCollection with these lists. Pretty much each list represents one day worth of orders. So I have modified the code like this:
Code behind
public class Order
{
public string OrderId { get; private set}
}
public ObservableCollection<List<Order>> ObservableResults
{
get
{
return new ObservableCollection<Order>
{
new List<Order>
{
new Order("Foo")
},
new List<Order>
{
new Order("Bar")
}
};
}
}
But I do have a problem with the XAML part right now... I am not really sure, how to achieve the following:
It will be a single scroll view, where each List<Order> will be encapsulated in its own Expander (so I can open close each day individually). So far I have this:
XAML version 2:
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Background="Red">
<ListView Background="Transparent" ItemsSource="{Binding ObservableResults}">
<ListView.ItemTemplate>
<DataTemplate>
<Expander>
<Expander.Header>
<TextBlock Text="{Binding Path=(ItemsControl.AlternationIndex), RelativeSource={RelativeSource AncestorType=ListViewItem}" />
</Expander.Header>
<ListView ItemsSource="{Binding}">
<ListView.View>
<GridView>
<GridViewColumn Header="Order ID" Width="650" DisplayMemberBinding="{Binding OrderId, UpdateSourceTrigger=PropertyChanged}" />
</GridView>
</ListView.View>
</ListView>
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
But in this case no data are present in the view and the ReSharper says, that it:
Cannot resolve symbol 'OrderId'
Can anyone help me, what's wrong in here?
My MVVM WPF application currently has a GridView that binds to a ViewModel property and has the columns defined in the XAML:
<ListView Grid.Row="0" ItemsSource="{Binding GroupedOrders}">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Item2, Mode=OneWay}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Date" DisplayMemberBinding="{Binding Item1.Date, StringFormat={}{0:dd/MM/yyyy}}" />
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Item1.Name}" />
<!-- lots more -->
</GridView>
</ListView.View>
</ListView>
GroupedOrders is an ObservableCollection of a Tuple of two different item types: an "Order" object and a Boolean that controls whether or not, for this particular view, it is "selected".
However, that "selected" property hasn't been modelled well. It's come to light that each Order needs multiple "selected" properties, depending on the number of dates in the order, which can be between one and five.
To model this in the UI, I need to replace that single Checkbox GridViewColumn with a dynamic construct that creates a similar Checkbox GridviewColumn for each date in the order. So GroupedOrders becomes a Tuple <Order, List<bool>> instead, and there will need to be one column for each bool in the List.
At any given instance, the size of that list will be the same for all the Orders in the grid. But if the user loads new data into the grid, the size of the list will change.
However, I cannot see how to do this in MVVM. Existing solutions seem to be for code-behind where the GridView can be grabbed as an object and manipulated on the fly. The only MVVM solution I've seen to this is to use a Converter to building the entire GridView on the fly, which would seem to be massive overkill for this situation, where there are a lot of columns in the GridView but only a small number need to be dynamic.
Is there another way?
Not sure this is what you expect, but this is what I can think of.
View
<ListView ItemsSource="{Binding Orders}">
<ListView.View>
<GridView>
<GridViewColumn Header="State">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding States}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.Resources>
<Style TargetType="GridViewColumnHeader">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Value}" Content="{Binding Text}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="OrderNo" DisplayMemberBinding="{Binding OrderNo}" />
</GridView>
</ListView.View>
</ListView>
Code Behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Orders.Add(new Order { OrderNo = "Order001" });
Orders.Add(new Order { OrderNo = "Order002" });
Orders.Add(new Order { OrderNo = "Order003" });
}
private readonly ObservableCollection<Order> _orders = new ObservableCollection<Order>();
public ObservableCollection<Order> Orders
{
get { return _orders; }
}
}
public class Order
{
public Order()
{
States.Add(new State { Text = "Is Paid", Value = false });
States.Add(new State { Text = "Is Delivered", Value = false });
}
public string OrderNo { get; set; }
private readonly ObservableCollection<State> _states = new ObservableCollection<State>();
public ObservableCollection<State> States
{
get { return _states; }
}
}
public class State
{
public string Text { get; set; }
public bool Value { get; set; }
}
Result
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>