WPF listbox binding to List<string> or List<string[]> - wpf

I'm very new to WPF (especially XAML) *
Hello, my app has a class which gets bunch of comma separated string. So I made a List collection to store the strings. Also, I made DataTemplate for the listbox. Here is code.
MainWindow.xaml
...
<DataTemplate x:Key="DataTemplate">
<Grid>
<StackPanel Grid.Column="1" Margin="5">
<StackPanel Orientation="Horizontal" TextBlock.FontWeight="Bold" >
<TextBlock Text="{Binding AAA}" />
</StackPanel>
<TextBlock Text="{Binding BBB}" />
<TextBlock Text="{Binding CCC}" />
</StackPanel>
</Grid>
</DataTemplate>
...
<ListBox x:Name="listbox1" HorizontalAlignment="Left" Height="309" Margin="10,10,0,0" Width="216" BorderThickness="1" VerticalAlignment="Top" ItemTemplate="{DynamicResource HeadlineDataTemplate}"/>
MainWindow.xaml.cs
...
MyClass myClass;
public MainWindow()
{
InitializeComponent();
myClass = new MyClass();
}
...
private void Button_Click(object sender, RoutedEventArgs e)
{
myClass.getData(Textbox1.Text); // I want to convert this to add items to listbox1.
}
MyClass.cs
...
public void getData(string target)
{
List<string> itemList = new List<string>();
...
while(result != null)
{
// This loop gives bunch of comma separated string (more than 100)
itemList.Add(result);
}
// after the loop, itemList has
// itemList[0] = {"AAA1,BBB1,CCC1"}
// itemList[1] = {"AAA2,BBB2,CCC2"}
// itemList[2] = {"AAA3,BBB3,CCC3"}
// ...
// I also can store the strings to List<string[]> using string.split().
}
So how should I do this?
I couldn't find the answer on the internet.

I suggest to create a model class to represent each ListBox Item. In this example I name it MyListBoxItemModel :
public class MyListBoxItemModel
{
public string AAA { get; set; }
public string BBB { get; set; }
public string CCC { get; set; }
}
Then in the getData function, create List of MyListBoxItemModel to be listbox1's ItemsSource :
public void getData(string target)
{
List<MyListBoxItemModel> itemList = new List<MyListBoxItemModel>();
...
while(result != null)
{
var splittedResult = result.Split(',');
itemList.Add(new MyListBoxItemModel{
AAA = splittedResult[0],
BBB = splittedResult[1],
CCC = splittedResult[2]
});
}
listbox1.ItemsSource = itemList;
}
Note that this example only demonstrates minimum amount of code needed to get your data displayed in the ListBox. Not involving various techniques and best practices around WPF development, such as implementing INotifyPropertyChanged interface, MVVM pattern, etc.

you can just bind to public properties, so if you write something like
<TextBlock Text="{Binding BBB}" />
you need an object with a public property "BBB".
so in addition to the answer from har07 you should also use a public property for your list(OberservableCollection)
public OberservableCollection<MyListBoxItemModel> ItemList {get;set;}
then your binding for your itemscontrol can look like this
<ListBox ItemsSource="{Binding ItemList}" ItemTemplate="{DynamicResource HeadlineDataTemplate}"/>
all you need now is the right datacontext ;)

Related

How to add a Rich Text Box to a Tab item so that can be added to a Tab Control in MVVM?

How to add a RichTextBox to a Tab item so that can be added to a Tab Control and display corresponding content in the RichTextBox dynamically in MVVM format.
ViewModel
private ObservableCollection<TabItem> TabControl()
{
ObservableCollection<TabItem> Tabs= new ObservableCollection<TabItem>();
return Tabs;
}
Controller
private void AddNewTabItem(string selectedItem)
{
try
{
System.Windows.Controls.RichTextBox richtextbox = new System.Windows.Controls.RichTextBox();
richtextbox.Name = "richtextbox" + selectedItem;
BrushConverter BC = new BrushConverter();
richtextbox.Background = (SolidColorBrush)(BC.ConvertFrom("#FF098BBB"));
richtextbox.Foreground = System.Windows.Media.Brushes.WhiteSmoke;
richtextbox.IsReadOnly = true;
TabItem m_tabItem = new TabItem();
m_tabItem.Header = selectedItem;
m_tabItem.Name = "tab" + selectedItem;
if (TabControl.Items.Count == 0)
{
TabControl.Items.Insert(0, m_tabItem);
TabControl.SelectedIndex = msgTracerTabControl.Items.Count - 1;
}
else
{
TabControl.Items.Insert(msgTracerTabControl.Items.Count - 1, m_tabItem);
TabControl.SelectedIndex = msgTracerTabControl.Items.Count - 2;
}
m_tabItem.Content = new System.Windows.Controls.RichTextBox();
m_tabItem.Content = richtextbox;
Tabs.add(m_tabItem);
}
catch (Exception EX)
{
}
}
View
<TabControl Grid.Column="1" Grid.Row="1" ItemsSource="{Binding TabControl}" }"/>
I have used this code and working fine and this is not in MVVM this is WAF Architecture in that i'm using MVVM concept.
You're not thinking MVVM. In a ViewModel you would not directly access UI elements but would rather set up bindings and data templates which would render your Viewmodels correctly. The correct approach is to have 2 viewmodels, 1 to act as a master and the second to act as the underlying DataContext for each tab.
A simple example would be something like this:
MainViewModel
public class MainViewModel : BindableBase
{
private int _tabSuffix;
public ObservableCollection<TextViewModel> TextTabs { get; set; } = new ObservableCollection<TextViewModel>();
public DelegateCommand AddNewTabCommand { get; set; }
public MainViewModel()
{
AddNewTabCommand = new DelegateCommand(OnAddNewTabCommand);
}
private void OnAddNewTabCommand()
{
TextTabs.Add(new TextViewModel()
{
Header = $"Tab #{_tabSuffix++}"
});
}
}
MainView
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="Add new tab item" Command="{Binding AddNewTabCommand}"></Button>
<TabControl Grid.Row="1"
ItemsSource="{Binding TextTabs}"
IsSynchronizedWithCurrentItem="True">
<!-- Defines the header -->
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type so44497239:TextViewModel}">
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<!-- defines the context of each tab -->
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type so44497239:TextViewModel}">
<RichTextBox Background="#FF098BBB" Foreground="WhiteSmoke" IsReadOnly="False" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
TextViewModel
public class TextViewModel : BindableBase
{
public string Header { get; set; }
public Brush BackgroundBrush { get; set; }
public Brush ForegroundBrush { get; set; }
public string Document { get; set; }
}
In this example, the main viewmodel has no knowledge of the View but merely adds items to it's own ObservableCollection. The TabControl itself, through the binding to TextTabs, adds it's own tab items and renders them using the ItemTemplate and ContentTemplate properties.
Download code here

Listbox not updating when items added or removed from ObservableCollection

I have a "Hello World" type application.
A listbox.ItemsSource = Players. Players is an ObservableCollection of Player that implements INotifyPropertyChanged.
When I add items in the Players constructor the items are displayed fine.
When I update an existing item the changes are reflected.
But when I add or remove items for the ObserveableCollection the listbox does not reflect the Items added / removed.
xaml in MainPage:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox Name="lstPlayers" ItemsSource="{Binding}" Margin="12,66,13,24" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<!--<StackPanel Background="#FFD1D1D1">-->
<Grid Margin="0,0,0,0" Grid.RowSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Number}" FontSize="48" />
<TextBlock Grid.Column="1" Text="{Binding Name}" FontSize="48" />
</Grid>
<!--</StackPanel>-->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Button" Height="95" HorizontalAlignment="Left" Margin="183,350,0,0" Name="button1" VerticalAlignment="Top" Width="260" Click="button1_Click" />
<Button Content="Button" Height="72" HorizontalAlignment="Left" Margin="198,472,0,0" Name="button3" VerticalAlignment="Top" Width="232" Click="button3_Click" />
</Grid>
The listbox is bound in the code behind:
lstPlayers.ItemsSource = plys;
Player Class:
using System.ComponentModel;
namespace DatabindList
{
public class TcrPlayer : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
_name = value;
NotifyPropertyChanged("Name");
}
}
}
}
Players class:
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace DatabindList
{
public class TcrPlayers : List<TcrPlayer>
{
public ObservableCollection<TcrPlayers> Items { get; private set; }
public TcrPlayers()
{
//these are displayed
Add(new TcrPlayer { Name = "Christine"});
Add(new TcrPlayer { Name = "Janet"});
Add(new TcrPlayer { Name = "Ian"});
}
public void AddPlayer()
{
//THIS HAS NO EFFECT ON THE LISTBOX
Add(new TcrPlayer { Name = "Newbie", Number = "99" });
}
}
}
I can UPDATE an existing item in the list by using the code behind in the MainPage with
var itm = lstPlayers.SelectedItem as TcrPlayer;
itm.Name = "Fred";
but I can't reflect any changes made to the number of items in the Players collection.
TcrPlayers derives from List<TcrPlayer> and adds items to itself. However, the ObservableCollection<TcrPlayer> property isn't connected to that list in any way whatsoever. Assuming that plys is a TcrPlayers you're not binding to an ObservableCollection at all - you're binding to a non-observable List.
Inheriting from ObservableCollection<TcrPlayer> instead of List<TcrPlayer> and removing the unused property might get you closer to where you want to be.
As an aside, you don't see this pattern used very often because inheriting from a collection class isn't generally considered very useful unless you're adding new functionality. Usually you'd just have an ObservableCollection<TcrPlayer> property of the parent viewmodel and bind the listbox's ItemsSource to that.
Speaking of which, you're also setting ItemsSource twice - in your XAML to {Binding} i.e. the current DataContext, and in the codebehind.
You don't use your ObservableCollection. Instead your TcrPlayers class is a list itself (derives from List<T>).
Either derive from ObservableCollection and remove your Items Property or use Items.Add(..) instead of Add().
E.g.
public class TcrPlayers : ObservableCollection<TcrPlayer>
{
..
or
public class TcrPlayers
{
public ObservableCollection<TcrPlayers> Items { get; private set; }
public TcrPlayers()
{
//these are displayed
Items.Add(new TcrPlayer { Name = "Christine"});
Items.Add(new TcrPlayer { Name = "Janet"});
Items.Add(new TcrPlayer { Name = "Ian"});
}

ListBox DataTemplate for dynamically loaded type

I have a sample MVVM WPF application and I'm having problems creating DataTemplates for my dynamically loaded model. Let me try explain:
I have the following simplified classes as part of my Model, which I'm loading dynamically
public class Relationship
{
public string Category { get; set; }
public ParticipantsType Participants { get; set; }
}
public class ParticipantsType
{
public ObservableCollection<ParticipantType> Participant { get; set; }
}
public class ParticipantType
{
}
public class EmployeeParticipant : ParticipantType
{
public EmployeeIdentityType Employee { get; set; }
}
public class DepartmentParticipant : ParticipantType
{
public DepartmentIdentityType Department { get; set; }
}
public class EmployeeIdentityType
{
public string ID { get; set; }
}
public class DepartmentIdentityType
{
public string ID { get; set; }
}
Here is how my View Model looks like. I created a generic object Model property to expose my Model:
public class MainViewModel : ViewModelBase<MainViewModel>
{
public MainViewModel()
{
SetMockModel();
}
private void SetMockModel()
{
Relationship rel = new Relationship();
rel.Category = "213";
EmployeeParticipant emp = new EmployeeParticipant();
emp.Employee = new EmployeeIdentityType();
emp.Employee.ID = "222";
DepartmentParticipant dep = new DepartmentParticipant();
dep.Department = new DepartmentIdentityType();
dep.Department.ID = "444";
rel.Participants = new ParticipantsType() { Participant = new ObservableCollection<ParticipantType>() };
rel.Participants.Participant.Add(emp);
rel.Participants.Participant.Add(dep);
Model = rel;
}
private object _Model;
public object Model
{
get { return _Model; }
set
{
_Model = value;
NotifyPropertyChanged(m => m.Model);
}
}
}
Then I tried creating a ListBox to display specifically the Participants Collection:
<ListBox ItemsSource="{Binding Path=Model.Participants.Participant}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<!-- WHAT TO PUT HERE IF PARTICIPANTS HAVE DIFFERENT PROPERTY NAMES -->
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
The problem is:
I don't know how to create a template that can handle both type of ParticipantTypes, in this case I could have EmployeeParticipant or DepartmentParticipant so depending on that, the data binding Path would be set to Employee or Department properties accordingly
I though about creating a DataTemplate for each type (e.g. x:Type EmployeeParticipant) but the problem is that my classes in my model are loaded dynamically at runtime so VisualStudio will complain that those types don't exist in the current solution.
How could I represent this data in a ListBox then if my concrete types are not known at compile time, but only at runtime?
EDIT: Added my test ViewModel class
You can still create a DataTemplate for each type but instead of using DataType declarations to have them automatically resolve you can create a DataTemplateSelector with a property for each template (assigned from StaticResource in XAML) that can cast the incoming data item to the base class and check properties or otherwise determine which template to use at runtime. Assign that selector to ListBox.ItemTemplateSelector and you'll get similar behavior to what DataType would give you.
That's not a good view-model. Your view-model should be view-centric, not business-centric. So make a class that can handle all four cases from a visual perspective, then bridge your business classes over to that view-model.
EDIT:
Working off your code:
<ListBox ItemsSource="{Binding Path=Model.Participants}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<TextBlock Text={Binding Id} />
<TextBlock Text={Binding Name} />
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I changed the binding, I assume that was a mistake?
I would create a ViewModel for Participant:
public class Participant_VM : ViewModelBase
{
private string _name = string.Empty;
public string Name
{
get
{
return _name ;
}
set
{
if (_name == value)
{
return;
}
_name = value;
RaisePropertyChanged(() => Name);
}
private string _id= string.Empty;
public string Id
{
get
{
return _id;
}
set
{
if (_id== value)
{
return;
}
_id = value;
RaisePropertyChanged(() => Id);
}
}
}
Modify the ListBox as follows.
<ListBox ItemsSource="{Binding Model.Participants.Participant}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type loc:DepartmentParticipant}">
<Grid>
<TextBlock Text="{Binding Department.ID}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type loc:EmployeeParticipant}">
<Grid>
<TextBlock Text="{Binding Employee.ID}"/>
</Grid>
</DataTemplate>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<!-- WHAT TO PUT HERE IF PARTICIPANTS HAVE DIFFERENT PROPERTY NAMES -->
<ContentPresenter Content="{Binding }"/>
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Edit:
loc refers to the namespace in which the DepartmentParticipant and EmployeeParticipant are present. Hope you are familiar with adding namespaces.

data grid binding in silverlight

I have a simple class abc
class abc
{
public string a { get; set; }
public string b { get; set; }
public string c { get; set; }
public abc(string d, string e, string f)
{
a = d;
b = e;
c = f;
}
}
public MainPage()
{
InitializeComponent();
abc obj = new abc("abc1", "abc2", "abc3");
LayoutRoot.DataContext = obj;
}
and a grid which contain three textbox 1 2 3 I am trying to bind these 3 properties of a class to a grid usercontrol.
<Grid x:Name="LayoutRoot" Background="White">
<TextBox Height="27" HorizontalAlignment="Left" Margin="125,86,0,0" Name="textBox1" Text="{Binding Path= a}" VerticalAlignment="Top" Width="120" />
<TextBox Height="25" HorizontalAlignment="Left" Margin="21,192,0,83" Name="textBox2" Text="{Binding Path= b}" Width="120" />
<TextBox Height="25" HorizontalAlignment="Left" Margin="250,192,0,0" Name="textBox3" Text="{Binding Path= c}" VerticalAlignment="Top" Width="120" />
</Grid>
it doesn't show any error but it does not show any output to a screen,what specific problem it creating?
Try do not use "Path= " (with space) in the binding expression. Try use:
Text="{Binding a}"
"Path" is present hiddenly in binding expressions. You need read some resources about bindings.
First your type 'abc' should implement INotifyPropertyChanged.
public class abc : INotifyPropertyChanged
{
...
}
Then you need to raise the INotifyPropertyChanged.PropertyChanged event
private void RaiseProperty(string propertyName)
{
var handle = PropertyChanged;
if(handle != null)
{
handle(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _a;
public string a { get{ return _a;} set{ _a = value; RaiseProperty("a"); } }
....
This should work as you need to a mechanism to notify the Binding if you are using CLR proprties; and that mechanism is provided by INotifyPropertyChanged interface

Binding data to ComboBox WPF

I am newbie to WPF, and needs help to bind data into the ComboBox. The xaml file contains the tag below.
<UserControl x:Class="SKAT.Postfordeler.Client.UI.View.ChooseInboxView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="42" d:DesignWidth="598">
<Grid>
<StackPanel Orientation="Horizontal">
<ComboBox Name="_currentInbox" Width="180" Margin="5" Height="22" DataContext="{Binding}" />
<Label Content="Et job kører allerede i denne indbakke (1500 ud af 1700 poster behandlet)" Name="_currentProcess" Margin="5" Height="25" />
</StackPanel>
</Grid>
//Inbox class , this class was implemented in seperate project
namespace SKAT.Postfordeler.Shared.DataTypes
{
[DataContract]
public class Inbox
{
[DataMember]
public String Id { get; set; }
[DataMember]
public String Folder { get; set; }
[DataMember]
public Rule Rules { get; set; }
}
}
//This code is located in the controller, the Activate method will fire when the MainWindow was executed
public void Activate()
{
var configuration = _configurationManager.GetConfiguration();// this method gets the xaml file settings
_chooseInboxView.FillInboxes(configuration.Inboxes); // Inboxes data binds to combobox
}
and in the View code behind, I created a method to bind the data which contains a type of list
public void FillInboxes(List<Inbox> inboxes)
{
DataContext = inboxes;
}
But it won't works,Any help please?
I assume your Inbox class consists of two properties (for simplicity), but there may be any number of them:
public class Inbox
{
public int ID { get; set; }
public string Text { get; set; }
}
You write a DataTemplate, for example:
<Grid.Resources>
<DataTemplate x:Key="InboxTemplate">
<WrapPanel>
<TextBlock Text="{Binding Path=ID}"/>
<TextBlock>:</TextBlock>
<TextBlock Text="{Binding Path=Text}"/>
</WrapPanel>
</DataTemplate>
</Grid.Resources>
Then correct your ComboBox declaration like:
<ComboBox Name="_currentInbox" Width="180" Margin="5" Height="22" ItemsSource="{Binding}" ItemTemplate="{StaticResource InboxTemplate}" />
Finally you set DataContext of your ComboBox to your List<Inbox>:
public void FillInboxes(List<Inbox> inboxes)
{
_currentInbox.DataContext = inboxes;
}
EDIT: As you've asked for a simpler solution, you can just override ToString() method of your Inbox class:
protected override string ToString()
{
return ID.ToString() + ":" + Text;
}
Instead of DataContext={Binding} you should have ItemsSource={Binding}.
The data context for any frameworkelement in the visual tree is by default {Binding}.
<ComboBox Name="_currentInbox"
SelectedItem="Hoved"
Width="180"
Margin="5"
Height="22"
DisplayMemberPath="Name"
ItemSource="{Binding}" />
Also for the combobox to display text of the items correctly I suppose you need DisplayMemberPath too. I assumed the property from Inbox class that you need to display is Name. Please replace with your relevant property name.
If your Inbox class is like,
public class Inbox
{
public int ID { get; set; }
public string Text { get; set; }
}
And if you do not want to change your xmal, the code behind method should be like this,
public void FillInboxes(List<Inbox> inboxes)
{
_currentInbox.DisplayMemberPath = "Text"; // To display the 'Text' property in the combobox dropdown
//_currentInbox.DisplayMemberPath = "ID"; // To display the 'ID' property in the combobox dropdown
_currentInbox.DataContext = inboxes;
}

Resources