WPF, ObservableCollection and listbox - c#

I have following code
private static ObservableCollection<myColor> myItems = new ObservableCollection<myColor>();
myItems.Add(new myColor("red"));
When object myColor is this class
public class myColor
{
public string color { get; set; }
public myColor(string col)
{
this.color = col;
}
}
Trouble is when I try to show item in listbox
<ListBox Margin="12,52,12,12" Name="listBox1" ItemsSource="{Binding}" />
It only shows "myColor" object instead of "color" variable. How can I show variable from this object?

If you only want to display a string property of the item use DisplayMemberPath on the ListBox.
Otherwise use ItemTemplate, which you can set to a DataTemplate that defines how each of your items looks like and can be of any complexity (i.e. can include other controls).

First set the DataContext for the window, as shown below :
public Window1()
{
InitializeComponent();
myItems.Add(new myColor("red"));
myItems.Add(new myColor("green"));
//Set the DataContext for the window
this.DataContext = myItems;
}
Now change the XAML to:
<ListBox Margin="12,52,12,12" Name="listBox1" ItemsSource="{Binding}" DisplayMemberPath="color" />
That's all. It should solve your problem.

Related

C# WPF DataGrid Binding from another class

I have a window in which I want to autogenerate ObservableCollection from another class. When setting it up in back-end, everything works properly:
XAML
<DataGrid Name="ResidenceGrid" AutoGenerateColumns="True"/>
CS
public ResidenceWindow()
{
InitializeComponent();
ResidenceGrid.ItemsSource = Manager.ResidenceList;
}
But the moment I try to do it all in xaml, the DataGrid appears blank:
XAML
<DataGrid Name="ResidenceGrid" ItemsSource="{Binding Path=Manager.ResidenceList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="True"/>
CS
public ResidenceWindow()
{
InitializeComponent();
}
The ObservableCollection called from another class just in case:
static class Manager
{
public static ObservableCollection<Residence> ResidenceList { get; set; } = new ObservableCollection<Residence>();
}
Any idea what I'm missing here?
If you want to use Binding, you need to set DataContext inside your ResidenceWindow.
Ex:
public ResidenceWindow()
{
InitializeComponent();
this.DataContext = Manager;
}
https://www.wpf-tutorial.com/data-binding/using-the-datacontext/
You can bind to the static Manager.ResidenceList property like this:
<DataGrid Name="ResidenceGrid" ItemsSource="{x:Static local:Manager.ResidenceList}" AutoGenerateColumns="True"/>
And there is no reason to set the Mode of the binding for the ItemsSource property to TwoWay nor set the UpdateSourceTrigger to PropertyChanged.

How to delete a ListView Item by a function in UserControl Template?

I am Developing windows 10 Universal app with c#.
I have a UserControl that is the MyListview item template. Listview will Bind the data. In userControl,there is a button for Delete the usercontrol DependencyProperty Content(contain string Text, Name and int Id ).
Listview show the text of object and the button for remove it.
Now how can remove that item from my List by click on remove Button?
Update
my Data class:
class Data
{
public int Id { get; set; }
public string Text { get; set; }
}
my usercontrol.cs :
public Data Content
{
get { return (Data)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
// Using a DependencyProperty as the backing store for Content. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(Data), typeof(MyUserControl1), new PropertyMetadata(null));
usercontrol xaml:
<StackPanel>
<TextBlock x:Name="textBlock" Text="{Binding Content.Text, ElementName=textBlock}" />
<Button Click="Remove_Click"/>
</StackPanel>
my list implementing:
<Page.Resources>
<DataTemplate x:Key="ListViewTemplate">
<local:MyUserControl1 Content="{Binding}"/>
</DataTemplate>
</Page.Resources>
<Grid>
<ListView x:Name="ListView" ItemTemplate="{StaticResource ListViewTemplate}" />
</Grid>
and in the code behinde the Page I use an ObservableCollection<Data> items = new ObservableCollection<Data>();to set Listview.ItemsSource to it.
The main Problem is How to remove that item from the items in MyUsercontrol1
You wrote about binding so I'm assuming that in your XAML there is a following code or similar:
<ListView ItemSource = "{Bind SomeCollection"} ... />
If I'm right there is no much to do. If SomeCollection is of type ObservableCollection<T> it is enough to remove an item from SomeCollection and UI will be refreshed ''automatically''. To sum up:
Declare SomeCollection as ObservableCollection<T>.
In a command that is executed when Delete button is clicked (or in an event handler) simply call ObservableCollection<T>.Remove.
UPDATE
This code is not elegant but shows an idea. Firstly we need to modify Data class:
public class Data
{
public int Id { get; set; }
public string Text { get; set; }
public Action<Data> OnRemoveCallback { get; set; }
public void OnRemove()
{
OnRemoveCallback(this);
}
}
OnRemoveCallback will be used to inform ListView that a given data element should be removed. Remove_click handler in MyUserControl simply executes OnRemove:
private void Remove_Click(object sender, RoutedEventArgs e)
{
Content.OnRemove();
}
Finally, in the code behind of your Page we have to define a logic that will be responsible for actual removing data items from the list:
public void Remove(Data d)
{
((ObservableCollection<Data>) ListView.ItemsSource).Remove(d);
}
...
ListView.ItemsSource = new ObservableCollection<Data>()
{
new Data() {Id = 1, Text = "1", OnRemoveCallback = Remove},
new Data() {Id = 2, Text = "2", OnRemoveCallback = Remove}
};
Now your Page will be informed whenever Delete button is pressed and will do a job.
As I said it is not a perfect solution. Personally, I'll use MVVM pattern. Thanks do that XAML and C# will be seperated.

set SelectedItem of ComboBox to value of item in ListCollectionView

I have a ListCollectionView which holds a bunch of Items of object Scene. One of the properties in Scene is Location.
When I navigate through the ListCollectionView I want to set the value of the Location property as SelectedItem in a comboBox in the View. Each time I go to a different item in the ListCollectionView I want to show the new location as SelectedItem in the comboBox.
I know how to make this work in a regular TextBox and TextBlock, but not in a ComboBox.
ViewModel
public ListCollectionView SceneCollectionView { get; set; }
private Scene CurrentScene
{
get { return SceneCollectionView.CurrentItem as Scene; }
set
{
SceneCollectionView.MoveCurrentTo(value);
RaisePropertyChanged();
}
}
View
<ComboBox SelectedItem="{Binding SceneCollectionView/Location, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding AllLocations}}"/>
For textboxes following works perfectly, but not for comboBoxes
<TextBox Text="{Binding SceneCollectionView/Location, UpdateSourceTrigger=PropertyChanged}"/>
Any Idea how I can get the same behavior in for SelectedItem in ComboBox. I'm fairly new to coding in c#
If all Locations defined in the supplied collection for your ListCollectionView exist in the Locations defined in your AllLocations property, then your code should work.
For example the following code along with the ComboBox you currently defined in your Xaml works as you expect:
Xaml:
<Grid>
<ComboBox SelectedItem="{Binding SceneCollectionView/Location, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding AllLocations}"/>
<TextBox Text="{Binding SceneCollectionView/Location, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="SelectNext" Click="Button_Click"/>
</Grid>
Code:
public ListCollectionView SceneCollectionView { get; set; }
public List<string> AllLocations { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
var scenes = new List<Scene>();
scenes.Add(new Scene { Location = "location1"});
scenes.Add(new Scene { Location = "location2"});
scenes.Add(new Scene { Location = "location3" });
SceneCollectionView = new ListCollectionView(scenes);
AllLocations = new List<string> { "location1", "location2", "location3" };
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SceneCollectionView.MoveCurrentToNext();
}
In the above code, when you click the Button, Both ComboBox.SelectedItem and TextBox.Text changes to the next Item.Location defined in your SceneCollectionView.

WPF Binding in ComboBox with UserControl list

In two combobox A and B.
A's ItemsSource is Custom list. and B's ItemsSource is UserControl list.
When manually setting the SelectedItem, A combobox works well, but B combobox UI do not show the selected Item. (In debugging, SelectedItem's value mapping is right, but the combobox B's UI do not be changed.)
All the other structure is same between A and B. What is the reason?
MainWindow.xaml
...
<ComboBox ItemsSource="{Binding FruitList}" SelectedItem="{Binding SelectedFruit}"
DisplayMemberPath="FruitName" />
<Button Content="Button" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<ComboBox ItemsSource="{Binding UserControlList}" SelectedItem="{Binding SelectedUserControl}" DisplayMemberPath="ItemName" />
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="Button_Click2"/>
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
FruitList.Add(f1);
FruitList.Add(f2);
FruitList.Add(f3);
UserControlList.Add(u1);
UserControlList.Add(u2);
UserControlList.Add(u3);
}
Fruit f1 = new Fruit { FruitName = "Apple" };
Fruit f2 = new Fruit { FruitName = "Banana" };
Fruit f3 = new Fruit { FruitName = "Lemon" };
MyUserControl u1 = new MyUserControl { ItemName = "Apple" };
MyUserControl u2 = new MyUserControl { ItemName = "Banana" };
MyUserControl u3 = new MyUserControl { ItemName = "Lemon" };
ObservableCollection<Fruit> _FruitList = new ObservableCollection<Fruit>();
public ObservableCollection<Fruit> FruitList
{
get { return _FruitList; }
set
{
_FruitList = value;
OnPropertyChanged();
}
}
Fruit _SelectedFruit;
public Fruit SelectedFruit
{
get { return _SelectedFruit; }
set
{
_SelectedFruit = value;
OnPropertyChanged();
}
}
ObservableCollection<MyUserControl> _UserControlList = new ObservableCollection<MyUserControl>();
public ObservableCollection<MyUserControl> UserControlList
{
get
{
return _UserControlList;
}
set
{
_UserControlList = value;
OnPropertyChanged();
}
}
MyUserControl _SelectedUserControl;
public MyUserControl SelectedUserControl
{
get { return _SelectedUserControl; }
set
{
_SelectedUserControl = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string caller = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.SelectedFruit = f3;
}
private void Button_Click2(object sender, RoutedEventArgs e)
{
this.SelectedUserControl = u3;
}
}
public class Fruit
{
public string FruitName { get; set; }
}
}
UserControl
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public string ItemName { get; set; }
}
This is not the good way of achieving this. Better define the ItemTemplate for the combobox to have the UserControl in it like:
<ComboBox ItemsSource="{Binding ItemList}" SelectedItem="{Binding SelectedItem}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<myControls:MyUserControl/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
and define the class Item
public class Item
{
public string ItemName { get; set; }
}
ObservableCollection<Item> _ItemsList = new ObservableCollection<Item>();
public ObservableCollection<Item> ItemsList
{
get
{
return _ItemsList ;
}
set
{
_ItemsList = value;
OnPropertyChanged();
}
}
Here DataContext of your UserControl will be Item object. you can bind the ItemName within you user control to show it in anyway you want.
in your user control you can have:
<TextBlock Text="{Binding ItemName}"></TextBlock>
Since you have asked "What is the reason?":
The reason why the second combo box does not show any selection is that ComboBox handles items of type ContentControl specially. In the read-only selection box, it is not the ContentControl that is used to display the value, but the content of the ContentControl. Since a UserControl is a ContentControl, the content of the UserControl is displayed inside the selection box, and therefore you have lost the data context of the UserControl; in the end, an empty string is displayed even though SelectedItem contains a reference to the UserControl that still has a valid data context. (As far as I know this behavior is undocumented; but you can see that it works like this by examining the ComboBox's code on http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/ComboBox.cs, especially the UpdateSelectionBoxItem() method).
By setting IsEditable="True" on the second ComboBox, you can see that everything works fine if the combo box has no read-only selection box.
Therefore, you generally should avoid adding UI elements to combo boxes, especially if you are using the DisplayMemberPath property, i.e. if you never want to actually display the UI element.
The recommended way to display ComboBox items with non-standard appearance (e.g. with UserControls) is described in the answer of #nit.
If you, however, insist on passing a UserControl item list to the ComboBox, you might remove DisplayMemberPath and use something like this:
<ComboBox ItemsSource="{Binding UserControlList}" SelectedItem="{Binding SelectedUserControl}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Furthermore, in the constructor of your UserControl, you must place this line:
((FrameworkElement) Content).DataContext = this;
This is necessary to make sure that the correct data context is available in the read-only selection box, which only contains the content of the user control, not the user control itself.
Please note, that with the above example, the drop-down list contains text only (i.e. the item names), but the selection box will contain the fully rendered user control.

Extending WPF ListBoxItem for sourced items not working

If I create a class that extends WPF's ListBoxItem, create a list of these objects, try to bind the list to a ListBox's ItemsSource, the items will not display:
<ListBox ItemsSource="{Binding Path=LbData, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">
<ListBox.ItemTemplate>
<DataTemplate >
<TextBlock Text="{Binding Path=Display}" Margin="1" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
public partial class MainWindow : Window
{
public IEnumerable lbData = new List<LbItem>();
public IEnumerable LbData
{
get { return lbData; }
set { lbData = value; }
}
public MainWindow()
{
InitializeComponent();
LbData = new List<LbItem> { new LbItem("a"), new LbItem("b") };
}
}
public class LbItem : ListBoxItem
{
public string Display { get; private set; }
public LbItem(string v)
{
Display = v;
}
}
I'm new to WPF and don't see why this should be an issue. TIA
The ItemsSource normally is used for data-objects, and what you do there (setting the ItemTemplate) suggest that you should not make your object inherit from ListBoxItem at all, instead it should be a normal object (possibly implementing INotifyPropertyChanged if properties may change after creation). If the list changes it should implement INotifyCollectionChanged.
Because the items are already ListBoxItems the DataTemplate you set will be disregarded. There should be the following error in your Visual Studio Output-window:
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='LbItem'
try setting LbData before InitializeComponent(); or Implement INotifyPropertyChanged also you probably would want to bind to an ObservableCollection

Categories

Resources