I have a list of strings that I want to display on a menu. I used a Listbox and it works just that it won't let me highlight or copy/paste.
Here is my XAML
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="500"/>
<ColumnDefinition Width="500"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="450"/>
<RowDefinition Height="318"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="1" Grid.Column="1" x:Name="uiOCRData" />
</Grid>
Heres what I have in C#
List<string> lines = new List<string>();
uiOCRData.ItemsSource = lines;
Thanks for the help!
You must use a ListBox.ItemTemplate so that you can include a control inside your ListBox.
Since you want to be able to select text etc., the best option is to use a TextBox.
<ListBox Grid.Row="0" Name="uiOCRData">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=.}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
EDIT
Let's say you want to bind to a list of some class objects instead of a simple list of strings. Say your class looks like this:
public class Data
{
public int Id { get; set; }
public string Name { get; set; }
}
Then you can bind to any one of chosen Properties of the class like this:
<ListBox Grid.Row="0" Name="uiOCRData">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Width="100" Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Related
I am relatively new in WPF and am trying to understand the MVVM pattern and how data-binding works with ObservableCollection, in order to build the application I am working on with MVVM. I have created a sample of my application that has a MainWindow where, depending on which button the user presses, a different View (UserControl) is displayed. The general idea is that the user will have access to the data of some elements from a database (e.g.: Customers, Products, etc.) and will be able to add new and edit, or delete, existing ones.
So, there is a CustomerView, with its CustomerViewModel, and a ProductView, with its ProductViewModel respectively. Also, there are two classes (Customer.cs & Product.cs) that represent the Models. The structure of the project is displayed here.
The MainWindow.xaml is as follows:
<Window.Resources>
<DataTemplate DataType="{x:Type viewModels:CustomerViewModel}">
<views:CustomerView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:ProductViewModel}">
<views:ProductView DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20*"/>
<ColumnDefinition Width="80*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button x:Name="btnCustomers" Click="btnCustomers_Click" Content="Customers" Width="80" Height="50" Margin="10"/>
<Button x:Name="btnProducts" Click="btnProducts_Click" Content="Products" Width="80" Height="50" Margin="10"/>
</StackPanel>
<Grid Grid.Column="1">
<ContentControl Grid.Column="0" Content="{Binding}"/>
</Grid>
</Grid>
and the code behind MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public CustomerViewModel customerVM;
public ProductViewModel productVM;
public MainWindow()
{
InitializeComponent();
}
private void btnCustomers_Click(object sender, RoutedEventArgs e)
{
if (customerVM == null)
{
customerVM = new CustomerViewModel();
}
this.DataContext = customerVM;
}
private void btnProducts_Click(object sender, RoutedEventArgs e)
{
if (productVM == null)
{
productVM = new ProductViewModel();
}
this.DataContext = productVM;
}
}
Finally, the CustomerView.xaml is as follows:
<UserControl.Resources>
<viewModel:CustomerViewModel x:Key="customerVM"/>
<!-- Styling code here...-->
</UserControl.Resources>
<Grid DataContext="{StaticResource ResourceKey=customerVM}">
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="7*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<TextBlock Text="Customers" FontSize="18"/>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5*"/>
<ColumnDefinition Width="5*"/>
</Grid.ColumnDefinitions>
<ComboBox x:Name="cmbCustomers" Grid.Column="0" VerticalAlignment="Top"
IsEditable="True"
Text="Select customer"
ItemsSource="{Binding}"
DisplayMemberPath="FullName" IsSynchronizedWithCurrentItem="True">
</ComboBox>
<StackPanel Grid.Column="1" Margin="5">
<StackPanel Orientation="Horizontal">
<TextBlock Grid.Column="0" Text="Id:" />
<TextBlock Grid.Column="1" x:Name="txtId" Text="{Binding Path=Id}" FontSize="16"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Grid.Column="0" Text="Name:" />
<TextBlock Grid.Column="1" x:Name="txtFirstName" Text="{Binding Path=FirstName}" FontSize="16"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Grid.Column="0" Text="Surname:" />
<TextBlock Grid.Column="1" x:Name="txtLastName" Text="{Binding Path=LastName}" FontSize="16"/>
</StackPanel>
</StackPanel>
</Grid>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center">
<Button x:Name="btnAddNew" Content="Add New" Click="btnAddNew_Click"/>
<Button x:Name="btnDelete" Content="Delete Customer" Click="btnDelete_Click"/>
</StackPanel>
</Grid>
and the CustomerViewModel.cs:
public class CustomerViewModel : ObservableCollection<Customer>
{
public CustomerViewModel()
{
LoadCustomers();
}
private void LoadCustomers()
{
for (int i = 1; i <= 5; i++)
{
var customer = new Customer()
{
Id = i,
FirstName = "Customer_" + i.ToString(),
LastName = "Surname_" + i.ToString()
};
this.Add(customer);
}
}
public void AddNewCustomer(int id)
{
var customer = new Customer()
{
Id = id,
FirstName = "Customer_" + id.ToString(),
LastName = "Surname_" + id.ToString()
};
Add(customer);
}
}
Please note that the ProductView.xaml & ProductViewModel.cs are similar.
Currently, when the user presses the "Customers" or the "Products" button of the
MainWindow, then the respective View is displayed and the collections are loaded
according to the LoadCustomers (or LoadProducts) method, which is called by the
ViewModel's constructor. Also, when the user selects a different object from the
ComboBox, then its properties are displayed correctly (i.e. Id, Name, etc.). The
problem is when the user adds a new (or deletes an existing) element.
Question 1: Which is the correct and best way to update a changed Observable
Collection of an element and reflect its changes in the UI (Combobox, properties, etc.)?
Question 2: During testing this project I noticed that the constructor of the
ViewModels (consequently the LoadCustomers & LoadProducts method) are called twice. However, it is only called when the user presses the Customers or the
Products button respectively. Is it also called via the XAML data binding? Is
this the optimum implementation?
Your first question is basically a UX one, there is no correct or "best" way. You'll definitely end up using some sort of ItemsControl, but which one depends heavily on how you want your users to interact with it.
To your second question, you have a few mistakes in your code:
<viewModel:CustomerViewModel x:Key="customerVM"/> Instantiates a new view model, apart from the one that the main application created
Grid DataContext="{StaticResource ResourceKey=customerVM}" Then uses this "local" view model, ignoring the inherited one from the main application
That's why you see the constructor fire twice, you are constructing two instances! Eliminate the local VM and don't assign the DC on the grid. Other issues:
<views:ProductView DataContext="{Binding}"/> The DataContext assignment is total unnecessary, by virtue of being in the data template it's data context is already set up
<ContentControl Grid.Column="0" Content="{Binding}"/> Yuck, you should have a "MainViewModel" with a property that this uses. Don't make it be the whole data context
Lack of commands for your button clicks (related to the bullet above)
There is 3 kinds of Change Notification you need with Lists in MVVM:
Change Notificataions on every property of the list items.
Change Notification on the property exposing the list, in case the whole instance has to be replaced (wich is common because of 3)
Change Notification if elements are added to or removed from the collection. That is the only thing ObservableCollection takes care off. Unfortunately there is no Addrange option, so bulk operations wil lsmwap the GUI with Notifications. That is what Nr. 2 is there for.
As advanced option, consider exposing the CollectionView rather then the raw Collection. WPF GUI elements do not bind to raw Collections, only CollectionViews. But if you do not hand them one, they will create one themself.
<ListView Name="myList" Background="Transparent" Margin="15,88,15,15">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid Height="100">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Margin="5">
<TextBlock Name="xName" Text="{Binding Name}" FontSize="30" Margin="10,0,5,0" FontWeight="Medium"/>
<TextBlock Name="xNo" Text="{Binding No}" FontSize="25" Margin="10,0,5,0" TextTrimming="CharacterEllipsis"/>
</StackPanel>
<Grid Grid.Column="1" Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<RadioButton Grid.Column="0" Name="r1" Content="1" Width="10" Margin="5,15,-2,5"/>
<RadioButton Grid.Column="1" Name="r2" Content="2" Width="10" Margin="5,15,-2,5"/>
</Grid>
</Grid>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Now I want to retrieve each item in ListView i.e, the controls in it. The TextBlocks content and to know which RadioButton is selected.
I tried taking classes like this
public class Att
{
public string Name;
public string No;
public RadioButton r1;
public RadioButton r2;
}
There are separate sources for the content of the TextBoxes in the ListView. They come from different classes. I tried retrieving the items using above class Att.
But it is showing some type conversion errors. Please anyone help me! I'm a beginner.
Thanks is advance.
Thanks for all the support!
Well I tried doing this on my own and finally I got it. What I really wanted is to know which RadioButton is checked. I tried taking class Att for retrieving the items in ListView. I made modifications to the Att Class and added properties r1 and r2 of type bool instead of RadioButton and binded this property to the IsChecked property of the RadioButton in XAML and also mode of binding is TwoWay. So the changes done in the XAML can be reflected in the source list of items.
I'm fairly new to C# and WPF, but I started building an application which should have a function of listing Items with a few details. Currently it looks like
The data for these 'items' (one item is multiple labels enclosed in a border (at a later time I would like to add a picture as well)) is loaded in through a REST Service and I don't know how many items will be responded. Now I have the problem of not beeing able to create the labels statically within the xaml because of the variating number of items recieved.
My question here is, how can I create multiple labels (as well as a border and an image) programmatically, align them correctly in the window and address the labels to fill them with data?
Thanks a lot for your help!
As you indicated in your comment, a ListBox will probably suit your purposes. The general idea is that you want to specify the format of the ListBox via an ItemTemplate, and in the ItemTemplate specify a DataTemplate with the necessary controls to support your API data, with bindings specified to a model mapped from the JSON. An incomplete example of the XAML:
<ListBox x:Name="transactionsListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness=".5" Padding="1">
<StackPanel>
<Label x:Name="id" Content="{Binding Path=Id}" />
<Label x:Name="item_id" Content="{Binding Path=Item_Id}" />
<Label x:Name="price" Content="{Binding Path=Price}" />
<Label x:Name="quantity" Content="{Binding Path=Quantity}" />
<Label x:Name="created" Content="{Binding Path=Created}" />
<Label x:Name="purchased" Content="{Binding Path=Purchased}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The Path= property of the bindings above need to match the property names of the model you create to store the transactions from your REST call. Then, when you have an instance of a list of that model type, your code would want to do something like:
List<Transaction> myTransactions = apiCall.response.mapToMyModel() // pseduocode
transactionsListBox.DataContext = myTransactions;
To add to Bobby's answer, here's an example using an ItemsControl.
<ItemsControl ItemsSource="{Binding SellTransactions}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding created}"></Label>
<Label Grid.Column="1" Content="{Binding id}"></Label>
<Label Grid.Column="2" Content="{Binding item_id}"></Label>
<Label Grid.Column="3" Content="{Binding price}"></Label>
<Label Grid.Column="4" Content="{Binding purchased}"></Label>
<Label Grid.Column="5" Content="{Binding quantity}"></Label>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The class for the transaction:
public class SellTransaction
{
public long id { get; set; }
public int item_id { get; set; }
public int price { get; set; }
public int quantity { get; set; }
public DateTime created { get; set; }
public DateTime purchased { get; set; }
}
I am creating a little application that has one feature to show list of people in popup when user click on button. That list, that is in popup, can be pretty big, it can have as many as 3k entries. I am using virtualization, and I don't have problem with performance when list is drawn. But, when user open application for the first time and click on button for popup with list of people, it can take 2-5 seconds before popup is shown. After, that if user try to open popup again popup will be open without delay.
So my question is could I say to ListView to prepare items while it is not shown. Because there is very big chance that user will use application for quiet some time before it will need this popup.
Can I optimize this in some other way? Is there some collection that is better for this purpose in WPF?
Also people collection will be populated when page is loaded, and popup is at beginning closed.
Here it is code that I have:
public class AddressBookViewModel : BaseViewModel
{
...
private ObservableCollection<PeopleModel> people;
...
public ObservableCollection<PeopleModel> People
{
get { return people; }
}
}
public class PeopleModel
{
public PeopleModel(string address, string name)
{
Address = address;
Name = name;
}
public string Name{ get; set; }
public string Address { get; set; }
}
<Button x:Name="btnChoosePerson"
Command="{Binding TogglePeopleAddressPopupCommand}"
Content="..." />
<Popup MaxHeight="520"
IsOpen="{Binding ShowPeopleAddressPopup}"
Placement="Bottom"
PlacementTarget="{Binding ElementName=btnChoosePerson}"
StaysOpen="False">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="5" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListView
Grid.Row="2"
Width="500"
HorizontalAlignment="Left"
ItemsSource="{Binding People}"
SelectionMode="Single"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.CacheLength="10"
VirtualizingPanel.CacheLengthUnit="Item">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
HorizontalAlignment="Left"
Text="{Binding Address}" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Right"
Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Popup>
I have a DataTemplate in which I have a Grid layout with 2 RowDefinitions. I have a TextBox in the first row and a ComboBox in the second row.
I have defined the DataTemplate in my ResourceDictionary.
This is the code for the DataTemplate:
<DataTemplate x:Key="myDataTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Name ="txtChannelDescription" Grid.Row="0" Margin="1,1,1,1"/>
<ComboBox Name="cmbChannelTag" Grid.Row="1" IsReadOnly="True" Margin="1,1,1,1"/>
</Grid>
</DataTemplate>
I am using this DataTemplate in code behind as:
(DataTemplate)FindResource("myDataTemplate")
How do I set the Value of TextBox.Text and the ItemSource of the ComboBox at runtime?
I am using the DataTemplate as template for DataGridTemplateColumn.Header.
Create a custom data type that suits your purpose (with properties named ChannelDescription and ChannelTag in this example) and then bind the values to your DataTemplate:
<DataTemplate x:Key="myDataTemplate" DataType="{x:Type NamespacePrefix:YourDataType}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Text="{Binding ChannelDescription}" Grid.Row="0" Margin="1,1,1,1"/>
<ComboBox ItemsSource="{Binding ChannelTag}" Grid.Row="1" IsReadOnly="True"
Margin="1,1,1,1"/>
</Grid>
</DataTemplate>
It would be used like this:
In your view model, you would have a collection property, lets say called Items:
public ObservableCollection<YourDataType> Items { get; set; }
(Your property should implement the INotifyPropertyChanged interface unlike this one)
In your view, you would have a collection control, lets say a ListBox:
<ListBox ItemsSource="{Binding Items}" ItemTemplate="{StaticResource myDataTemplate}" />
Then each item in the collection control will have the same DataTemplate, but the values would come from the instances of type YourDataType in the Items collection.