This question already has answers here:
Why does WPF support binding to properties of an object, but not fields?
(2 answers)
Closed 5 days ago.
I would like to get comfortable again with C# so I decided to start with an "easy" one. I would like create a dictionary from a XML file and bind it to a combobox. These are my snippets:
XML
<configuration>
<Paths>
<add key="Path_DQM_CE_Prod" value="DQM-CE\DQM_CE_12.2.0_17615\QMConfigurationEditor.exe" />
<add key="Path_DQM_CE_Test" value="DQM-CE\QMConfigurationEditor.exe" />
</Paths>
</configuration>
Code-behind (works, I an iterate through the dictionary)
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
///read XML and store into dictionary
var xdoc = XDocument.Load(#"\\ServerXYZ\Settings_CAX_Startcenter\config.xml");
var Paths = xdoc.Root.Descendants("Paths").Elements()
.ToDictionary(a => (string)a.Attribute("key"),
a => (string)a.Attribute("value"));
}
XAML
<Grid>
<StackPanel HorizontalAlignment="Left">
<ComboBox ItemsSource="{Binding Paths}" DisplayMemberPath="Value.Key" />
</StackPanel>
</Grid>
Pretty sure my binding is wrong but Im unable to solve it
Paths must be a public property for you to be able to bind to it:
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
///read XML and store into dictionary
var xdoc = XDocument.Load(#"\\ServerXYZ\Settings_CAX_Startcenter\config.xml");
Paths = xdoc.Root.Descendants("Paths").Elements()
.ToDictionary(a => (string)a.Attribute("key"),
a => (string)a.Attribute("value"));
}
public Dictionary<string, string> Paths { get; }
You would then set the DisplayMemberPath to the name of the property of the KeyValuePair<TKey, TValue> that you want to display in the ComboBox, i.e. "Key" or "Value":
<ComboBox ItemsSource="{Binding Paths}" DisplayMemberPath="Value" />
Related
This question already has an answer here:
ItemsControl ItemTemplate Binding
(1 answer)
Closed 2 months ago.
I have a user control StudentView.xaml that is something like this:
<Grid>
<TextBlock Text="{Binding StudentName}"/>
<TextBlock Text="{Binding StudentID}"/>
</Grid>
I also have a model Student.cs that is something like this:
private string _StudentName = null;
public string StudentName{
get => _StudentName;
set {_StudentName = value; OnPropertyChange("StudentName");}
}
private string _StudentID = null;
public string StudentID{
get => _StudentID ;
set {_StudentID = value; OnPropertyChange("StudentID");}
}
In my ViewModel MainViewModel I have a collection of Student's like this:
ObservableCollection<Student> Students = new ObservableCollection<Student>();
// Do something here to populate Students
NumOfStudents = Students.Count();
In my main window, I have something like this:
<StackPanel>
<local:StudentView/>
</StackPanel>
I am very new to WPF and seeking for help. I know how to bind local:StudentView to one student. My question is that is there a way to use local:StudentView as many as time as NumOfStudents. For example, if I have 5 students, the main window will display StudentView 5 times.
What you most likely want is a ItemsControl. You will set the ItemsSource property to your observable collection and define a ItemTemplate to define how the items should be rendered (StudentView.xaml). Finally, since you are using a StackPanel, that will be defined as your ItemsPanelTemplate.
You'll want do something like...
<ItemsControl
ItemsSource="{Binding Students}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:StudentView/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Is there a way to find out (in code) if the ItemSource of the Datagrid was set via binding, or if there was an assignment?
Binding:
<DataGrid x:Name="MyGrid" ItemsSource="{Binding Foo}"/>
Assignment:
<DataGrid x:Name="MyGrid" />
MyGrid.ItemsSource = new List<int> { 1, 2, 3 };
there is a framework method to detect a Binding: BindingOperations.GetBindingExpression
Returns the BindingExpression object associated with the given property or null if none exists
var binding = BindingOperations.GetBindingExpression(MyGrid, DataGrid.ItemsSourceProperty);
string result = (binding == null) ? "assignment" : "binding";
[[1]: https://i.stack.imgur.com/waMYs.png][1]
The image shows how you can conclude that the grid is getting populated with the required values. Also MyGird.Items.Results View value in debugger displays the actual values. In your case Foo variable should be a list of int. The correct way of binding Foo is given below.
In code behind:
public List<int> Foo { get; set; }
public MainWindow()
{
InitializeComponent();
Foo = new List<int>() { 1, 2, 3 };
MyGrid.ItemsSource = Foo;
}
In XAML
<Grid>
<DataGrid x:Name="MyGrid">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
I have the following code:
SubjectsToChooseFrom = new ObservableCollection<SubjectDTO>(_model.Subjects);
SubjectsToChooseFrom = SubjectsToChooseFrom.Where(x => x.Id == (int)CurrentProgId);
SubjectsToChooseFrom is bound to a ComboBox. After this, only one element gets shown in the box instead of a list. Why is this, and how can I make sure all the values where x.Id == (int)CurrentProgId stay in SubjectsToChooseFrom?
UPDATE
XAML and code as requested:
<ComboBox Text="Choose program" Margin="5" Grid.Row="1" Grid.Column="1" ItemsSource="{Binding SubjectsToChooseFrom}"
SelectedValue="{Binding CurrentSubjectId, Mode=TwoWay}"
SelectedValuePath="Id"
DisplayMemberPath="SubjectName">
</ComboBox>
Declaration:
private IEnumerable<SubjectDTO> _subjectsToChooseFrom;
public IEnumerable<SubjectDTO> SubjectsToChooseFrom
{
get
{
return _subjectsToChooseFrom;
}
set
{
_subjectsToChooseFrom = value;
OnPropertyChanged();
}
}
Where returns an IQueryable. You need to use ToList() or ToArray() etc. to materialize the results of this query.
If you're using MVVM, much better is to expose both your collections as public properties and raise PropertyChanged whenever these collections change for any reason. You can then bind your ComboBoxes to these properties and they'll automatically get updated.
I ended up doing this by hand:
await _model.LoadAsync();
SubjectsToChooseFrom = new ObservableCollection<SubjectDTO>(_model.Subjects);
ObservableCollection<SubjectDTO> temp = new ObservableCollection<SubjectDTO>();
foreach (var subject in SubjectsToChooseFrom) {
if(subject.ProgId == CurrentProgId)
{
temp.Add(subject);
}
}
SubjectsToChooseFrom = temp;
Can I somehow represent list of lists in the DataGrid in WPF? If so, what is the easiest way to do it? btw I am unable to generate columns names by myself and I don't know how much fields will be in every list.
The following code gives me the "Capacity" and "Count" columns which is not supposed to show.
var list = new List<List<String>>();
var firstList = new List<String>();
firstList.Add("1");
firstList.Add("2");
var secondList = new List<String>();
secondList.Add("3");
secondList.Add("4");
list.Add(firstList);
list.Add(secondList);
excelData.ItemsSource = list;
Thanks in advance.
One way to do it is to use another DataGrid inside the RowDetailsTemplate of the first DataGrid like this:
<Grid Background="Blue">
<DataGrid x:Name="ExcelData" AutoGenerateColumns="True">
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid ItemsSource="{Binding SelectedItem,ElementName=ExcelData}" x:Name="DataGridDetail" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</Grid>
and in the code behind define the ListOfList Property :
private List<List<String>> _listOfList;
public List<List<String>> ListOfList
{
get
{
return _listOfList;
}
set
{
if (_listOfList == value)
{
return;
}
_listOfList = value;
}
}
and initialize it like you did :
ListOfList = new List<List<String>>();
var firstList = new List<String> { "1", "2" };
var secondList = new List<String> {"3", "4"};
ListOfList.Add(firstList);
ListOfList.Add(secondList);
ExcelData.ItemsSource = ListOfList;
You may consider using ObservableCollection instead of List and implement the INotifyPropertyChanged Interface for better flexibility
Yes <DataGrid.RowDetailsTemplate> <DataTemplate> should be used and also ObservableCollection. If your grid is readonly i.e. for display only then you can use List also
I'm using WPF and the timer doesn't allow to use int for interval. Instead, it asks for TimeSpan
timer1.Interval = TimeSpan.FromMilliseconds(Convert.ToDouble(comboBox1.SelectedItem));
So I changed my code to this but at runtime it gives me an InvalidCastException, saying that the object cannot be converted from System.Windows.Controls.ComboboxItem to System.IConvertible.
How can I solve this?
You should use this
Convert.ToDouble(comboBox1.SelectedText)
The comboBox1.SelectedItem corresponds to the selected item of the ComboBox control and not with the text of it, which is that you want.
Specifically, the SelectedText property of a CombBox control
Gets or sets the text that is selected in the editable portion of a ComboBox.
as it is stated here.
Update
Please use this one:
((ComboBoxItem)comboBox1.SelectedItem).Content.ToString();
Or in two steps:
ComboBoxItem item = (ComboBoxItem)comboBox1.SelectedItem;
timer1.Interval = TimeSpan.FromMilliseconds(Convert.ToDouble(item.Content.ToString()));
For more information about the ComboBoxItem class, please have a look here.
It appears that you are adding ComboBoxItems directly to your ComboBox.
A cleaner and safer approach than parsing strings would be to continue binding to SelectedItem, but to also bind the ItemsSource to a collection of integers.
Then use the ItemTemplate property of the ComboBox to define how to render the integers if you are not satisfied with the default ToString() rendering.
<ComboBox ItemsSource="{Binding Intervals}" SelectedItem="{SelectedInterval}">
<ComboBox.ItemTemplate>
<DataTemplate TargetType="{x:Type Int64}">
<TextBlock Text="{Binding}" Background="Red"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox>
With properties looking something like this:
public int SelectedInterval {get;set;}
public List<int> Intervals {
get {
var lst = new List<int>();
for(var i = 1000; i <= 10000; i += 500)
{
lst.Add(i);
}
return lst;
}
}
Now you have strongly type properties that you can manipulate without parsing.