Combobox binding WPF inside ListView - c#

Pulling my hair out here. I can't get my combobox within my listview to bind to a list in the code behind.
Also the combobox isn't even appearing within the column..
Want a combobox in listview to show numbers 0-24.
XAML:
<ListView Grid.Row="0" Margin="0,0,0,0" Height="250" Width="540" SelectionMode="Single" dd:DragDrop.IsDragSource="True" dd:DragDrop.IsDropTarget="True" x:Name="TasksList">
<ListView.View>
<GridView>
<GridViewColumn Header ="Day 1" Width="50">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=ComboBox1}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
And Code behind:
public partial class TaskHoursRemaining : Page {
List<int> hourOfDay = new List<int>();
public TaskHoursRemaining() {
InitializeComponent();
LoadData();
DataContext = this;
}
private void LoadData() {
for (int i = 0; i < 25; i++) {
hourOfDay.Add(i);
}
this.ComboBox1.ItemsSource= hourOfDay;
}
}
but ComboBox1 does not exist in the current context.

In your XAML, you're binding to a non-existent property ComboBox1:
<ComboBox ItemsSource="{Binding Path=ComboBox1}"/>
In your code-behind, you're accessing a non-existent field ComboBox1:
this.ComboBox1.ItemsSource= hourOfDay;
The DataContext = this; statement does nothing useful for you here.
To create fields via XAML, you should use the x:Name attribute. This wouldn't help you anyway, since, the ComboBox resides in a template.
#un-lucky is correct that you should bind the list view to the collection (which is in fact what you're trying to do in your code-behind). Then again, the ComboBox also wants a collection, so you should properly have a data model that is a collection of collections. (Sort of -- all the comboboxes want the same collection; only the selected item will differ.)
Let's first make this work with a TextBox instead of a ComboBox. The list binds to hourOfDay, while the TextBox displays the int:
private readonly List<int> hourOfDay = new List<int>();
public MainWindow()
{
InitializeComponent();
for (int i = 0; i < 25; i++)
{
this.hourOfDay.Add(i);
}
this.TasksList.ItemsSource = this.hourOfDay;
}
XAML:
<ListView Grid.Row="0" Margin="0,0,0,0" Height="250" Width="540" SelectionMode="Single" x:Name="TasksList">
<ListView.View>
<GridView>
<GridViewColumn Header ="Day 1" Width="50">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Mode=OneWay}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Result:
What you want, though, is a list of somethings, where each combobox has a dropdown with 1-24. I don't know what the somethings might be -- perhaps something like this:
public class Entry
{
private static readonly List<int> hourOfDay;
static Entry()
{
hourOfDay = new List<int>();
for (int i = 0; i < 25; i++)
{
hourOfDay.Add(i);
}
}
public IEnumerable<int> HourOfDaySource => hourOfDay;
}
In the window/page constructor:
InitializeComponent();
this.TasksList.ItemsSource = new List<Entry>
{
new Entry(),
new Entry(),
new Entry(),
new Entry(),
new Entry(),
};
XAML:
<ListView Grid.Row="0" Margin="0,0,0,0" Height="250" Width="540" SelectionMode="Single" x:Name="TasksList">
<ListView.View>
<GridView>
<GridViewColumn Header ="Day 1" Width="60">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="wpf:Entry">
<ComboBox
ItemsSource="{Binding HourOfDaySource, Mode=OneWay}"
SelectedIndex="12"
Width="42"
/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Result:
There's a goodly amount of plumbing required for this to become useful, but at least you've got your ComboBoxes populated...

Related

Can't change ComboBox selection when bound to ObservableCollection (WPF)

I'm trying to create an edit form for editing properties of a custom set of TV Series objects. One of the properties holds a collection of all owned media formats (DVD, Blu-ray, etc) for that particular series that will be displayed in a ComboBox. Items are added to the ComboBox via a separate popup window and items are to be removed from the ComboBox by selecting the item and clicking a remove Button.
I can add new entries to the MediaOwned ComboBox just fine, but when I try to select a specific ComboBox item to test the remove Button I find that I can only ever select the first entry. Can someone please tell me if I've missed something embarrassingly obvious, thanks.
Here is the problematic property:
private ObservableCollection<string> _mediaOwned = new ObservableCollection<string>();
public ObservableCollection<string> MediaOwned
{
get { return _mediaOwned; }
set
{
_mediaOwned = value;
OnPropertyChanged(new PropertyChangedEventArgs("MediaOwned"));
}
}
Here are the other relevant code behind:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Create binding for the ListBox.
Binding listBinding = new Binding();
listBinding.Source = show.Series;
listBinding.Mode = BindingMode.OneWay;
listBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
lbSeries.SetBinding(ListBox.ItemsSourceProperty, listBinding);
// Create binding for the ComboBox.
Binding myBinding = new Binding();
myBinding.Path = new PropertyPath("MediaOwned");
myBinding.Mode = BindingMode.TwoWay;
myBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
cbMediaOwned.SetBinding(ComboBox.ItemsSourceProperty, myBinding);
}
private void btnRemoveMedia_Click(object sender, RoutedEventArgs e)
{
Series series = (Series)lbSeries.SelectedItem;
series.MediaOwned.Remove(cbMediaOwned.Text);
}
And here is the XAML code:
<Border Style="{StaticResource PanelBorderStyle}" DockPanel.Dock="Left" Margin="0,8,8,0"
DataContext="{Binding ElementName=lbLists, Path=SelectedItem}">
<DockPanel VerticalAlignment="Top">
<StackPanel>
<ListBox x:Name="lbSeries" Style="{StaticResource BasicListStyle}" Width="180" Height="300"
DisplayMemberPath="Title" SelectionMode="Single" LayoutUpdated="lbSeries_LayoutUpdated">
</ListBox>
</StackPanel>
<StackPanel x:Name="editPanel" DataContext="{Binding ElementName=lbSeries, Path=SelectedItem}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0, 4, 0, 0">
<TextBlock Style="{StaticResource SmallFont}" Width="100">Title</TextBlock>
<TextBox x:Name="txtTitle" Style="{StaticResource TextBoxStyle}" Text="{Binding Path=Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="200" Margin="8, 8, 16, 8"></TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top">
<TextBlock Style="{StaticResource SmallFont}" Width="100">Media owned</TextBlock>
<ComboBox x:Name="cbMediaOwned" Style="{StaticResource ComboBoxStyle}" Width="150" Margin="8,8,6,8"
></ComboBox>
<Button x:Name="btnAddMedia" Style="{StaticResource ToolbarButtonStyle}" Click="btnAddMedia_Click" Margin="0">
<StackPanel ToolTip="Add media">
<Image Source="Images/add.png" />
</StackPanel>
</Button>
<Button x:Name="btnRemoveMedia" Style="{StaticResource ToolbarButtonStyle}" Click="btnRemoveMedia_Click" Margin="4">
<StackPanel ToolTip="Remove media">
<Image Source="Images/remove.png" />
</StackPanel>
</Button>
</StackPanel>
</StackPanel>
</DockPanel>
</Border>
Alternatively I can also remove the binding code in the code behind and replace the ComboBox with the below code (but I still get the same problem - I can't select anything in the ComboBox):
<ComboBox x:Name="cbMediaOwned" Style="{StaticResource ComboBoxStyle}" Width="150" Margin="8,8,6,8" ItemsSource="{Binding ElementName=lbSeries, Path=SelectedItem.MediaOwned, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedMedia, UpdateSourceTrigger=PropertyChanged}"></ComboBox>
SelectedMedia property:
private string _selectedMedia = "";
public string SelectedMedia
{
get { return _selectedMedia; }
set
{
_selectedMedia = value;
OnPropertyChanged(new PropertyChangedEventArgs("SelectedMedia"));
}
}
Here is my xaml:
<ComboBox x:Name="Models_ComboBox"
Width="110"
Text="Model"
ItemsSource="{Binding Models}"
SelectedItem="{Binding SelectedModel}"
DisplayMemberPath="Model"
MouseDoubleClick="Models_ComboBox_MouseDoubleClick"
SelectionChanged="Models_ComboBox_SelectionChanged"/>
Here are my VM properties:
private DataTable models;
public DataTable Models
{
get { return models; }
set
{
if (models != value)
{
models = value;
OnPropertyChanged(nameof(Models));
}
}
}
and
private DataRowView selectedModel;
public DataRowView SelectedModel
{
get { return selectedModel; }
set
{
if (selectedModel != value)
{
selectedModel = value;
if (value != null)
{
InitializeOptions(value["Model"].ToString());
}
OnPropertyChanged(nameof(SelectedModel));
}
}
}
As you can see, the ItemsSource and the SelectedItem of the ComboBox are bound to two different properties in the ViewModel. The ItemsSource is bound to a DataTable populated from a Database. Once the user selects a Model, then there are other option ComboBoxes that are populated based on that selection.
Fixed the problem myself. I had a line of code that was automatically setting the SelectedIndex of the ComboBox without me realizing.

Binding confusion in xaml

The source code can be found here : https://www.codeproject.com/Articles/24973/TreeListView
The way the original author has set it up, is the data is filled in the xaml itself. I need to create the TreeViewList inside of my ViewModel, but I can't figure out how to bind my own TreeViewList within the xaml to display it properly.
Here's an example of me creating a tree in the code behind and calling the window.
public TreeListView TreeList { get; set; } = new TreeListView();
private void generateTree()
{
TreeList.Columns.Add(new GridViewColumn() { Header = "Col1" });
TreeList.Columns.Add(new GridViewColumn() { Header = "Col2" });
TreeList.Columns.Add(new GridViewColumn() { Header = "Col3" });
}
public ICommand AssemblyTreeCommand => new RelayCommand(AssemblyTree, p => CanAssemblyTree);
public bool CanAssemblyTree { get; set; } = true;
private void AssemblyTree(object parameter)
{
generateTree();
AssemblyTreeDialogWindow dialog = new AssemblyTreeDialogWindow()
{
DataContext = this,
Topmost = true
};
dialog.ShowDialog();
}
AssemblyTreeDialog Window class looks like this:
<local:TreeListView AllowsColumnReorder="True" ItemsSource="{Binding TreeList}">
<!--Create an item template to specify the ItemsSource-->
<local:TreeListView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" />
</local:TreeListView.ItemTemplate>
<local:TreeListView.Columns>
<!--Create the first column containing the expand button and the type name.-->
<GridViewColumn Header="Name" Width="200">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--The Expander Button (can be used in any column (typically the first one))-->
<local:TreeListViewExpander/>
<!--Display the name of the DataElement-->
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!--Create a second column containing the number of children.-->
<GridViewColumn Header="Children" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<!--Display the size of the DataElement-->
<TextBlock Text="{Binding Children.Count}" HorizontalAlignment="Right"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!--Create a third column containing the brush of the material.-->
<GridViewColumn Header="Brush" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--Border showing the actual color-->
<Border Background="{Binding Brush}" CornerRadius="2"
Width="16" Height="16"
BorderThickness="1" BorderBrush="DarkGray"/>
<!--Display the brush-->
<TextBlock Text="{Binding Brush}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</local:TreeListView.Columns>
<!--Create some sample data-->
<MaterialGroup>
<MaterialGroup>
<DiffuseMaterial Brush="Blue"/>
<DiffuseMaterial Brush="Red"/>
<SpecularMaterial Brush="Orange"/>
</MaterialGroup>
<EmissiveMaterial Brush="AliceBlue"/>
</MaterialGroup>
</local:TreeListView>
Interestingly if I bind the line <GridViewColumn Header="Name" Width="200"> so that it reads <GridViewColumn Header="{Binding TreeList}" Width="200">it gives me this:
I'll explain my end goal as best as possible.
The System is a giant list of parts. A main table displays all of the parts while a subtable displays all of the parts which make up that part. All parts (including those which are used to create other parts) exist within the MainTable. So a Parent part might have a set of children parts, which each individually have children parts which they are made up of. This is the relationship i'm trying to model using this tool.
The code that I've written maps the parts list out into a list of class objects which contain the data. I'll post it below. It's working to map to a TreeView right now.
A datastructure I've based it off is here : Treeview with multiple columns
private void generateTree(string PN)
{
Proces selectedRow = new Proces() { procesId = (int)Vwr.Table.SelectedRow.Row["PID"], procesName = (string)Vwr.Table.SelectedRow.Row["PN"], subProcesses = generateSubtable(PN) };
processes.Add(selectedRow);
}
public List<Proces> generateSubtable(string PN)
{
List<Proces> subTable = new List<Proces>();
foreach (DataRow mplrow in Vwr.Table.Tbl.Rows) if (mplrow["PN"].ToString() == PN)
MainVM.Modules.AllModules[0].SubVwr.Tables[0].LoadTableQuery.Prms[0].Val = mplrow[0];
MainVM.Modules.AllModules[0].SubVwr.Tables[0].Tbl = Db.GetTable(MainVM.Modules.AllModules[0].SubVwr.Tables[0].LoadTableQuery);
foreach (DataRow sub in MainVM.Modules.AllModules[0].SubVwr.Tables[0].Tbl.Rows)
{
Proces subItem = new Proces() { procesId = (int)sub["ItemNo"], procesName = sub["PN"].ToString(), subProcesses = generateSubtable(sub["PN"].ToString()) };
subTable.Add(subItem);
}
return subTable;
}
Found the answer! After some pretty extensive searching, and trying many different solutions. Thought i'd post incase someone else might also be trying to do the same.
credit:
http://dlaa.me/blog/post/9898803

C# Listview DisplayMemberBinding shows "wrong" value

In my WPF app I have a Listview with a couple columns and one column with a checkbox inside.
The xaml is as follows:
<ListView x:Name="listviewImported">
<ListView.View>
<GridView>
<GridViewColumn Header="Check" Width="50">
<GridViewColumn.CellTemplate>
<DataTemplate>
/*This line may be the prob.*/ <CheckBox IsChecked="{Binding MarkedForCheck}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Pos." Width="75" DisplayMemberBinding="{Binding PositionAsString}" />
<GridViewColumn Header="Value" Width="175" DisplayMemberBinding="{Binding ImportValue}" />
<GridViewColumn Header="Type" Width="175" DisplayMemberBinding="{Binding ImportValueTypeDescription}" />
</GridView>
</ListView.View>
</ListView>
The ItemSource is assigned like this:
listviewImported.ItemsSource = _catalog.ImportedList;
The ImportedList in the _catalog is of Type ImportedElem:
private List<ImportedElem> _importedList = new List<ImportedElem>();
This is the ImportedElem class:
public class ImportedElem : BaseElem {
public ImportedElem(int Position, string ImportValue) : base(Position, ImportValue) {
}
//MARKED FOR CHECK
public bool MarkedForCheck = true;
}
This is the problem:
When I add an ImportedElem to the List, then the Listview will update and the columns "Pos.", "Value" and "Type" contain the correct Data. However, the Column with the checkbox ("Check") always shows a nonchecked Checkbox after adding it to the List. The Data is correct, the column just shows the wrong Data. I can modify the checkbox in the list and it will update the Data, that is good. But the List should contain the correct Data right after adding the Element to the list, wich it is not doing. It would be very interesting to know why 3 columns are correct and the checkbox doesn't work.
change public bool MarkedForCheck = true; TO public bool MarkedForCheck {get;set;}

Dynamically Adding Items To a WPF List View

I am trying to programatically add items to a ListView in WPF. I have done a lot of reading (including some questions here) and thought I was doing it correctly but the items aren't showing up. As I understand it I create the ListViewe and bind it to a data source, in this case an ObservableCollection. I have verified the ObservableCollection is getting items added to it, but they aren't getting displayed on the ListView. If it matters, the ListView is already instantiated by the time I run the LINQ query and attempt to add items to it.
Here is the XAML that defines the list view:
<TabPanel Name="ResultsTab" Height="200" Width ="500" DockPanel.Dock="Top" HorizontalAlignment="Left">
<TabItem Name="Default_Tab" Header="Default">
<ListView Name="DefaultListView" ItemsSource="Binding FCPortCollection">
<ListView.View>
<GridView x:Name="DefaultGridView">
<GridViewColumn Width="Auto" Header="FC Port" DisplayMemberBinding="{Binding Path=FCPort}" />
<GridViewColumn Width="Auto" Header="WWPN" DisplayMemberBinding="{Binding Path=WWPN}"/>
<GridViewColumn Width="Auto" Header="FCID" DisplayMemberBinding="{Binding Path=FCID}" />
<GridViewColumn Width="Auto" Header="SwitchName" DisplayMemberBinding="{Binding Path=SwitchName}">
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</TabItem>
And here is the code that is supposed to load it.
public class PortResult
{
public string SwitchName;
public string FCPort;
public string FCID;
public string WWPN;
public PortResult(string name, FCPort port)
{
SwitchName = name;
FCPort = String.Format("fc{0}/{1}", port.SlotNum, port.PortNum);
WWPN = port.WWPNList[0].WWPNValue;
FCID = port.WWPNList[0].FCIDValue;
}
}
ObservableCollection<PortResult> FCPortCollection = new ObservableCollection<PortResult>();
// results is an IEnumerable collection of FCPort result from a LINQ query that has been turned into a Dictionary
foreach (KeyValuePair<string, List<FCPort>> resultspair in results)
{
foreach (FCPort port in resultspair.Value)
{
// create a new PortResult and add it to the ObservableCollection
PortResult pr = new PortResult(resultspair.Key, port);
FCPortCollection.Add(pr);
}
}
There are several problems in the code you posted:
The binding syntax for your ItemsSource is missing the {} braces - it needs to be ItemsSource="{Binding FCPortCollection}"
You can only bind to properties, however you only expose fields in your PortResult class. Change those fields to be properties.
Also make sure the DataContext of the ListView is set to the object which contains the FCPortCollection. Also make sure the collection is a property of the object and not a field (same reason as point 2. above).
This:
ItemsSource="Binding FCPortCollection"
Is not a binding, you forgot the braces {} and hence assigned a char[] as ItemsSource instead.

Binding ComboBox ItemsSource in DataGrid RowDetailsTemplate

I am trying to bind an ItemsSource to a ComboBox in a RowDetailsTemplate. If I place a ComboBox outside the grid it works fine. I think this is occureing because of the ItemsSource property on the grid may be throwing off the ComboBox within the RowDetailsTemplate. XAML is below any thoughts?
Categories and CatTypes are two different ObservableCollections.
No error is occurring; the ComboBox just appears empty.
<ComboBox ItemsSource="{Binding CatTypes}"></ComboBox>
<my:DataGrid Name="gridProds" AutoGenerateColumns="False"
AlternatingRowBackground="Gainsboro" ItemsSource="{Binding Categories}">
<my:DataGrid.Columns>
<my:DataGridTextColumn x:Name="CatId" Header="CatID" Width="Auto" Binding="{Binding CategoryID}" />
<my:DataGridTextColumn Header="CatName" Width="Auto" Binding="{Binding CategoryName}" />
</my:DataGrid.Columns>
<my:DataGrid.RowDetailsTemplate>
<DataTemplate>
<Border>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label>ID:</Label>
<TextBox Name="txtGridCatId" Text="{Binding CategoryID}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label>Category Type:</Label>
<ComboBox ItemsSource="{Binding CatTypes}"></ComboBox>
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</my:DataGrid.RowDetailsTemplate>
</my:DataGrid>
There is a class in the called DataSource in which the following is done:
private ObservableCollection<string> _cattypes = new ObservableCollection<string> { };
public ObservableCollection<string> CatTypes
{
get
{
_cattypes = new ObservableCollection<string> { };
SqlConnection con = new SqlConnection("MyConnStringHere;");
SqlCommand cmd = new SqlCommand("Select ID, CatType from PfCategoryType ORDER BY CatType", con);
con.Open();
SqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
string CatType = (string)rdr["CatType"];
_cattypes.Add(CatType);
}
con.Close();
return _cattypes;
}
}
In the MainWindow.xaml.cs I have:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataSource dataSource = new DataSource();
this.DataContext = dataSource;
}
}
If you checked the debug output in VS you would see the actual binding error. Most likely below code will fix it for you.
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=CatTypes}" />
If you can't get RelativeSource to work then use names. The property CatTypes is a property of some class which you created an object for and set as datacontext to some control. Just give that control a name (for example myControl) and bind like this:
<ComboBox ItemsSource="{Binding ElementName=myControl, Path=CatTypes}" />
If that don't work you need to post more of your code to figure out what you are doing wrong.
What happens if you try this?
<ComboBox DataContext="{Binding DataContext, ElementName=myControl}" ItemsSource="{Binding CatTypes}" />
(Of course you'd rename "myControl" to match the name of your window.)
Here, we're setting the data context of the combo box to be the same as the data context of the window. Since this is also the same data context of the first combo box in your XAML, I imagine the second combo box will start behaving like the first. (Although I worry that this will result in some unnecessary database connections, one per grid row.)
On second thought, if you need to set other properties in the context of the row, you won't want to set the data context of the entire ComboBox. In that case, I'd try something like this.
<ComboBox ItemsSource="{Binding ElementName=myControl, Path=DataContext.CatTypes}" SelectedItem="{Binding CategoryType}" />

Categories

Resources