I have ItemsControlwhich use template to bind data.
<ItemsControl ItemsSource="{Binding MyCollection}" x:Name="MyCollectionControl" ItemTemplate="{DynamicResource MyCollectionTemplate}" />
MyCollection is type of NameValueCollection and The following binding does not work. It is populating correct number of pairs but TextBlock does not get the bounded value.
Template
<DataTemplate x:Key="MyCollectionTemplate">
<Grid>
<TextBlock Text="{Binding Path=Value, Mode=OneWay}"/>
<TextBox Name="CValue"/>
</Grid>
</DataTemplate>
mainWindow
string[] dataCollection=new string[5];
....
....
Student studentObject=new Student("1",dataCollection);
this.dataContext = studentObject;
Student Class
public class Student
{
public string Id;
public NameValueCollection MyCollection {get; set;}
public Student(string id, params string[] additionalInfo)
{
Id = id;
if (additionalInfo != null)
{
MyCollection=new NameValueCollection();
foreach (string s in MyCollection)
{
string[] tokens = s.Split('|');
if (tokens.Length == 2)
MyCollection.Add(tokens[0], tokens[1]);
}
}
}
}
What is the wrong I am doing when binding NameValueCollection.
Please advice me.
OK a couple of things, one you probably want to change your DataTemplate a bit since you are overlaying a textbox directly over your textblock, for my test I just changed it to a stack panel:
<StackPanel>
<TextBlock Text="{Binding}"/>
<TextBox Name="CValue"/>
</StackPanel>
Also notice I changed to simply Text="{Binding}" since the items within the NameValueCollection are simply strings and don't have a value attribute.
Also not sure if this was just another typo, but this:
foreach (string s in MyCollection)
{
string[] tokens = s.Split('|');
if (tokens.Length == 2)
MyCollection.Add(tokens[0], tokens[1]);
}
should probably say:
foreach (string s in additionalInfo)
{
string[] tokens = s.Split('|');
if (tokens.Length == 2)
MyCollection.Add(tokens[0], tokens[1]);
}
otherwise you are just iterating over an empty collection.
Related
I am looking for a way to know if my listview contains a value. Below is my code.
public class OnlineUserList
{
public string Name { get; set; }
public string Color { get; set; }
}
<ListView x:Name="lvOnlineUsers" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" SelectionMode="Single" SelectionChanged="lvOnlineUsers_SelectionChanged">
<ListView.View>
<GridView x:Name="lvOnlineUsersGridView" AllowsColumnReorder="False">
<GridViewColumn Header="Online Users" Block.TextAlignment="Center" TextOptions.TextFormattingMode="Display" TextBlock.FontWeight="Bold">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Name="tbOnlineUsersGridView" Text="{Binding Path=Name}" Foreground="{Binding Path=Color}" HorizontalAlignment="Center" VerticalAlignment="Center" TextOptions.TextFormattingMode="Display" Style="{StaticResource ResourceKey=lblLabel}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
public void AddUserToList(string username)
{
lvOnlineUsers.Items.Add(new OnlineUserList { Name = username, Color = "Black" });
}
Now this is where am having issue
public void RemoveUserFromList(string username)
{
if(lvOnlineUsers.Items.Contains(username))
lvOnlineUsers.Items.Remove(username);
}
You should learn MVVM.
In the mean time, put the items in an ObservableCollection and assign it to the listview's ItemsSource property in your codebehind. Thereafter, repeat after me: Never, ever touch lvOnlineUsers.Items. Never, never, never. Forget that it exists. Everything you do, you interact with the ObservableCollection. Search it, add items to it, remove items from it. The UI will magically and mysteriously update itself.
I'm going to assume this is in MainWindow. If this is in a different view, the constructor will have a different name.
public MainWindow()
{
InitializeComponent();
lvOnlineUsers.ItemsSource = _onlineUsers;
}
private ObservableCollection<OnlineUserList> _onlineUsers
= new ObservableCollection<OnlineUserList>();
public void AddUserToList(string username)
{
_onlineUsers.Add(new OnlineUserList { Name = username, Color = "Black" });
}
public void RemoveUserFromList(string username)
{
// We don't search _onlineUsers for the string username, because
// _onlineUsers doesn't contain strings. It contains your user class.
// So instead, we look for the user class instance that has the name
// we want.
var found = _onlineUsers.FirstOrDefault(ou => ou.Name == username);
if (found != null)
{
_onlineUsers.Remove(found);
}
}
Until you have looked into MVVM, try this:
for(int i = lvOnlineUsers.Items.Count - 1; i >= 0; --i)
{
OnlineUserList item = lvOnlineUsers.Items[i] as OnlineUserList;
if (item != null && item.Name == username)
lvOnlineUsers.Items.Remove(lvOnlineUsers.Items[i]);
}
I have a combobox declared in my xaml.
I have a ExtendedComboBoxItem : ComboBoxItem defined which has properties DisplayName and DisplayImage.
In my user control, I have a comboBox control where i defined a data template that is essentially a Image | TextBock
In code-behind, I ALWAYS ADD same 3 items to it, so I do something like:
List<ExtendedComboBoxItem > items = new List<ExtendedComboBoxItem >();
ExtendedComboBoxItem item1 = new ExtendedComboBoxItem ("A","imagePath");
ExtendedComboBoxItem item2 = new ExtendedComboBoxItem ("A","imagePath");
ExtendedComboBoxItem item3 = new ExtendedComboBoxItem ("A","imagePath");
items.Add(item1);
items.Add(item2);
items.Add(item3);
this.comboBox.ItemsSource = items;
Is there a XAML only way of doing the above or a better cleaner way to do this?
thanks!
I'm not 100% sure that this will work - there are some ifs and buts regarding the type you can put in the array: https://msdn.microsoft.com/en-us/library/ms753379%28v=vs.110%29.aspx#Requirements_for_a_Custom_Class_as_a_XAML_Element .
You can set up an array in the Resources section of your XAML:
<Window.Resources>
<x:Array Type="mynamespace:ExtendedComboBoxItem"
x:Key="MyArray">
<mynamespace:ExtendedComboBoxItem MyProp1="A" MyProp2="imagePath" />
<mynamespace:ExtendedComboBoxItem MyProp1="A" MyProp2="imagePath" />
<mynamespace:ExtendedComboBoxItem MyProp1="A" MyProp2="imagePath" />
</x:Array>
</Window.Resources>
...
<ComboBox ItemsSource="{StaticResource MyArray}" />
You can define a DataTemplate for the Items in the combobox, Refer below code.
<ComboBox x:Name="cbo">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding DisplayImage}"/>
<TextBlock Text="{Binding DisplayName}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObservableCollection<ComboItem> combolst = new ObservableCollection<ComboItem>();
for (int i = 0; i < 10; i++)
{
combolst.Add(new ComboItem() { DisplayName = "Name" + i, DisplayImage = "Icon.png" });
}
cbo.ItemsSource = combolst;
}
}
public class ComboItem
{
public string DisplayName { get; set; }
public string DisplayImage { get; set; }
}
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 ;)
I want to bind my Datatemplate to 2 Datasources, one datasource that will actually define what is in the ListBox and other that will determine how many ListBoxes are there and what Items in the Listbox are selected\checked.
I have following XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="TokenListTemplate">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="chkToken" IsChecked="{Binding Path=IsSelected, Mode=TwoWay}">
<TextBlock Text="{Binding Path=Text}" />
</CheckBox>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ItemTemplate">
<Border BorderThickness="1">
<StackPanel Margin="3">
<TextBlock Text="{Binding Path=Header}"/>
<ListBox ItemTemplate="{StaticResource TokenListTemplate}"
ItemsSource="{Binding Path=Tokens}" >
</ListBox>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{Binding}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
And this is the codebehind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObservableCollection<DataEntity> _actualObjects;
List<Token> tokens1 = new List<Token>()
{
new Token("1"),
new Token("2"),
new Token("3"),
new Token("4")
};
List<Token> tokens2 = new List<Token>()
{
new Token("11"),
new Token("21"),
new Token("31")
};
_actualObjects = new ObservableCollection<DataEntity>()
{
new DataEntity(tokens1, "A", "1,2,3", 1),
new DataEntity(tokens1, "B", "2,3", 1),
new DataEntity(tokens2, "C", "21,31", 2)
};
DataContext = _actualObjects;
}
class DataEntity
{
public DataEntity(List<Token> tokens, string header, string tokenString, int entityTypeId)
{
Tokens = tokens;
Header = header;
TokenString = tokenString;
EntityTypeId = entityTypeId;
}
public List<Token> Tokens { get; set; }
public String Header { get; set; }
public String TokenString { get; set; }
public int EntityTypeId { get; set; }
}
public class Token
{
public bool IsSelected { get; set; }
public string Text { get; set; }
public Token(string text)
{
this.IsSelected = false;
this.Text = text;
}
}
}
It produces this
I don't want to inject token1 or token2 List into DataEntity object so in other words I want DataEntity constructor to be
public DataEntity(string header, string tokenString, int entityTypeId)
Listbox DataTemplate should select
tokens1 List as datasource for its LisBoxItems if
Dataentity.EntityTypeId = 1
tokens2 List as datasource for its LisBoxItemsif
DataEntity.EntityTypeId = 2
Also TokenString in DataEntity should be bound to items in the Listbox i.e. if Listbox shows 1 2 3 4
and DataEntity for this listbox has its TokenString value set to "1,2,3" then 1 2 3 should be checked in the listbox
I would recommend to create a ViewModel as a layer between your model and the view. In the ViewModel you can arrange the data to fit to the used controls without changing your model.
So the ViewModel could for example split the tokenString of the DataEntity into a list of tokens.
Just Google for MVVM (Model-View-ViewModel) for examples and furter explanations or look here on SO (like MVVM: Tutorial from start to finish?).
You're not thinking about this correctly. You need to create one class (some may call a view model) with the responsibility of providing all of the data that the view (or UI) will need. Therefore, you will need to have one property which holds a collection of type DataEntity (if I understand you correctly) to 'define what is in the outer ListBox' as you say.
Then you need a DataTemplate to describe what should be displayed for each item in the ListBox - your 'ItemTemplate' template. This DataTemplate should have another ListBox inside in which to display your Token objects. Your DataEntity should have something like this property in it:
public List<Token> Tokens
{
get
{
if (EntityTypeId == 1) return tokens1;
else if (EntityTypeId == 2) return tokens2;
}
}
You will then need another DataTemplate for your Token objects - your 'TokenListTemplate' template, but without the StackPanel... the inner ListBox replaces that, eg. if there are two Token objects in one DataEntity object, then that object would show two Checkboxes... you have correctly bound the IsChecked property to the Token.IsSelected property.
This may be complicated, but it is entirely possible. Just start with the first layer and get your DataEntity objects displayed in the outer ListBox using your 'ItemTemplate' template. Once that bit is ok, move on to the inner ListBox. Good luck.
I made a CollectionToStringConverter which can convert any IList into a comma-delimited string (e.g. "Item1, Item2, Item3").
I use it like this:
<TextBlock Text="{Binding Items,
Converter={StaticResource CollectionToStringConverter}}" />
The above works, but only once when I load the UI. Items is an ObservableCollection. The text block does not update, and the converter does not get called when I add or remove from Items.
Any idea what's missing to make this work?
The binding is to the property yielding the collection. It will come into effect whenever the collection instance itself changes, not when items in the collection are changed.
There are quite a few ways to achieve the behavior you want, including:
1) Bind an ItemsControl to the collection and configure the ItemTemplate to output the text preceded by a comma if it's not the last item in the collection. Something like:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<TextBlock>
<TextBlock Visibility="{Binding RelativeSource={RelativeSource PreviousData}, Converter={StaticResource PreviousDataConverter}}" Text=", "/>
<TextBlock Text="{Binding .}"/>
</TextBlock>
</ItemsControl.ItemTemplate>
</ItemsControl>
2) Write code in your code-behind to watch the collection for changes and update a separate property that concatenates the items into a single string. Something like:
public ctor()
{
_items = new ObservableCollection<string>();
_items.CollectionChanged += delegate
{
UpdateDisplayString();
};
}
private void UpdateDisplayString()
{
var sb = new StringBuilder();
//do concatentation
DisplayString = sb.ToString();
}
3) Write your own ObservableCollection<T> subclass that maintains a separate concatenated string similar to #2.
Converter will get called only when the Property changes. In this case the 'Items' value is not changing. When you add or remove new items in to the collection the binding part is not aware of that.
You can extend the ObservableCollection and add a new String property in it.Remember to update that property in your CollectionChanged event handler.
Here is the Implementation
public class SpecialCollection : ObservableCollection<string>, INotifyPropertyChanged
{
public string CollectionString
{
get { return _CollectionString; }
set {
_CollectionString = value;
FirePropertyChangedNotification("CollectionString");
}
}
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
string str = "";
foreach (string s in this)
{
str += s+",";
}
CollectionString = str;
base.OnCollectionChanged(e);
}
private void FirePropertyChangedNotification(string propName)
{
if (PropertyChangedEvent != null)
PropertyChangedEvent(this,
new PropertyChangedEventArgs(propName));
}
private string _CollectionString;
public event PropertyChangedEventHandler PropertyChangedEvent;
}
And your XAML will be like
<TextBlock DataContext={Binding specialItems} Text="{Binding CollectionString}" />