Dynamically creating ComboBox items, filling them with data, and naming them? - c#

I want to detect how many files are in a directory, then use that number to add the same number of items to a ComboBox. But when I create the items, how do I give each item an individual name? The 'Add' only gives the item its content, but I want to give it an x:Name. This is what I have so far:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
if (!File.Exists(#"C:\_Kooper Young FBLA\CIC=true.txt"))
{
int FileAmount = Directory.GetFiles(#"C:\_Kooper Young FBLA").Length;
for (int i = 1; i < FileAmount + 1; i++)
{
ComboBox.Items.Add();
}
}
File.Create(#"C:\_Kooper Young FBLA\CIC=true.txt");
}

As you are in WPF, I would better advise you to use Binding for your ComboBox.
Here is a simple way to do it, creating a class ComboItem :
public class ComboItem
{
public string FileName { get; set; }
}
So in your code you just need to do the following :
public ObservableCollection<ComboItem> ComboFiles { get; }
= new ObservableCollection<ComboItem>();
foreach (var file in Directory.GetFiles(directoryName))
{
ComboFiles.Add(new ComboItem { FileName = file });
}
so you can populate your list of ComboBox.
Then in your xaml you can bind to your ObservableCollection like that :
<ComboBox Grid.Column="1" ItemsSource="{Binding Path=ComboFiles}"
SelectedItem="{Binding Path=SelectedFile}" DisplayMemberPath="FileName"/>
Where SelectedFile is a ComboItem Object.
Of course the answer is not "key in hand", but making a bit of research about Binding I am sure you will reach it quickly, I only can advise you that link

I hope I'm understanding your question correctly. You can try to create a Model for your files, whatever they represent. I'll say PersonModel and then try ObservableCollection<PersonModel> People = new ObservableCollection<PersonModel>();
On your PersonModel you'll need properties for whatever information you need to store about a Person like Name.
Then write a foreach loop that looks at each item in your directory and creates a PersonModel for each one found, and sets the Name property somehow.
Finally at the end, but still inside the loop, do People.Add(item);
In Xaml bind the ItemsSource property of your combobox to the People collection. Set DisplayMemberPath of your combobox to Name.

Related

Wpf DataGrid Set Columns dynamically by list property

I have a small caliburn micro MVVM project with a DataGrid. The columns will consist of x amount of 'setup's and the rows will consist of 'CustomRow'. I would like to use a ObservableCollection where CustomRow has a function property and a collection of setup property. For each setup in this collection a column should exist with the value of setup.
class CustomRow
{
public string Function { get; set; }
public ObservableCollection<Setup> Setups { get; set; }
}
// example class
class Setup
{
public string Name { get; set; }
public object Content { get; set; }
}
So I need to be able to add columns and rows dynamically depending on the itemssource (all the Setups collections will have the same size).
My problem is that I don't know how to translate the Setups property into multiple columns.
I have spend quit some time on what should be a mundane problem in my opinion. But I am missing something.
Any help is much appreciated.
Bind or set the ItemsSource to the ObservableCollection<CustomRow> and then get the Setups property of the first CustomRow in the source collection in the view, e.g.:
private void Window_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var vm = DataContext as YourViewModel;
dataGrid.Columns.Clear();
if (vm.Rows != null && vm.Rows.Count > 0)
{
var setups = vm.Rows[0].Setups;
foreach (var setup in setups)
{
dataGrid.Columns.Add(new DataGridTextColumn { Header = setup.Name, Binding = new Binding("Content ") });
}
}
dataGrid.ItemsSource = vm.Rows;
}
There is no way to bind the Columns property of the DataGrid directly to a source property so you need to create the columns yourself one way or another.
You should do this in the view, in the control or in an attached behaviour that is attached to either of them. A view model should not create any columns.

WPF DateGrid: How to bind a column of ComboBox plus need to enable/disable entries dynamically

This part is ready and operational:
"How to bind a column of ComboBox"
<DataGridTemplateColumn Header="Bot Plate Thickness">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding SteelThickness, RelativeSource={RelativeSource AncestorType=Window}}" SelectedItem="{Binding BottomPlateThickness, UpdateSourceTrigger=PropertyChanged}" SelectionChanged="ComboBox_SelectionChanged" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
And this is the relevant part of my model:
public class GridModel : PropertyChangedBase
{
private string _BottomPlateThickness;
public string BottomPlateThickness
{
get
{
return _BottomPlateThickness;
}
set
{
if (_BottomPlateThickness != value)
{
_BottomPlateThickness = value;
RaisePropertyChanged("BottomPlateThickness");
}
}
}
}
This is another part:
public List<string> SteelThickness { get; set; }
SteelThickness = new List<string> { "0.3750", "0.4375", "0.5000", "0.6250", "0.7500", "0.8650", "1.0000" };
As you can see, the ComboBox contents are based on a static list. I read that in order to be able to be turned on/off, I have to base that column on an ObservableList<object>.
TIA.
The answer is simple... you don't access a ComboBox in a specific row of a DataGridTemplateColumn. Why do so many people want to use WPF like WinForms?... it's not WinForms. I say this line so much that I'm starting to feel like a preacher... WPF is a data-centric language... that means that we manipulate data objects, not UI objects. Why not make your life easier and hold the metaphoric hammer by the handle instead of the head?
Thinking of this problem in terms of data, we have a collection of data objects. One object is represented by one row in the DataGrid, so one property of these objects will relate to the selected item from the ComboBoxes in question. Now if you were to make your life really easy, you'd add a new collection property into that class that you could bind to the ComboBox.ItemsSource property.
Now, to change the possible options in each ComboBox, all you need to do is to change the items in that collection property in your data bound object. You could define your 'unfiltered' collection in your data type as a static variable that each instance could share:
private static ObservableCollection<string> steelThickness = new
ObservableCollection<string> { "0.3750", "0.4375", "0.5000", "0.6250", "0.7500",
"0.8650", "1.0000" };
public ObservableCollection<string> ComboBoxItemsSource
{
get { return new ObservableCollection<string>(
steelThickness.Where(item => item.MeetsCertainCriteria(item))); }
}
private bool MeetsCertainCriteria(string item)
{
// return true or false for each item to adjust the members in the collection
// based on whatever your condition is
if ( ... ) return true;
return false;
}
The only points to note with this set up is that you'll have to manually call NotifyPropertyChange("ComboBoxItemsSource") when the collection is updated and that you'll need to use a OneWay Binding because it has no setter. Now, wasn't that easier?

Changing a property of an ObservableCollection in a ViewModel without selecting it

In my program I have a TreeView that is implemented through a ViewModel by using an ObservableCollection. Each collection has a property called Rank. This is supposed to serve as the collection item's index.
In this question, I was able to get my TreeView nodes to switch places using ObservableCollection.Move();
However, after switching the places of the nodes, I need to correct/change the value of the nodes' rank, so that I can continue to manipulate them.
This should help explain what I am doing:
View -- Code-Behind:
//Button Click Event -- This makes the Selected Node switch places with the node above it
private void shiftUp_Click(object sender, RoutedEventArgs e)
{
//if a node is selected
if (UCViewModel.TreeViewViewModel.SelectedItem != null)
{
//If the selected Node is not in the 0 position (can not move up anymore)
if (UCViewModel.TreeViewViewModel.Collection<TreeViewModel>.IndexOf(UCViewModel.TreeViewViewModel.SelectedItem) != 0)
{
int oldIndex = UCViewModel.TreeViewViewModel.SelectedItem.Rank;
int newIndex = oldIndex--;
UCViewModel.TreeViewViewModel.Collection<TreeViewModel>.Move(oldIndex, newIndex);
//**Pseudo code trying to explain what I want to do
//**get item at specific index and change the Rank value
//Collection item at index (newIndex).Rank -= 1;
//Collection item at index (oldIndex).Rank += 1;
}
}
}
UserControl -- XAML:
<TreeView ItemsSource="{Binding TreeViewViewModel.Collection<TreeModel>}" ... />
How can I correct the Rank values after the move?
EDIT
As stated above, I have a Rank property in the Data Model of my TreeView. #Noctis's answer recommends using that property to sort my TreeView after the Rank values are changed. This is demonstrated by my favorite question on this topic, here.
I've added the SortObservableCollection class to my program, so now all that's left is to manipulate rank values, and sort. Would the correct place to do this be from the code-behind? Basically where the above^ section is from? If that is the case, I get a little confused about the exact calls...
Code-Behind:
private void shiftUp_Click(object sender, RoutedEventArgs e)
{
//if a node is selected
if (UCViewModel.TreeViewViewModel.SelectedItem != null)
{
//Moves the selectedNode down one (Up visually, hence shiftUp)
UCViewModel.TreeViewViewModel.SelectedItem.Rank--;
//How would I get the node below the selected one and change the Rank?
//This would be the call to sort. Which needs to be called for the collection
//For some reason, sort does not come up for the collection...
//UCViewModel.TreeViewViewModel.Collection.**Sort(...);
}
}
Instead of actually moving your items around, you could change the actual values on your objects ranks (assuming they are public properties), and let the sorting be done automatically for you via the binding ...
Edit:
This is embarrassing, and as your comment says, my link is to winforms.
having said that, have a look at this wonderful answers by rachel, wpf-it, and the self answered jeremy .
They either implemented a comparer (the direction i was going to with the msdn link), or used the default headers.
That should give you a great start and example on how to do it with your rank.
Edit:
On the Xaml:
<StackPanel x:Name="LayoutRoot">
<TreeView Name="TestTreeView" ItemsSource="{Binding MyTree}">
<TreeView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1" Margin="2" Padding="2">
<TextBlock Text="{Binding Path=Name}"/>
</Border>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<Button Command="{Binding SortMe_Command}">Sort</Button>
On the view model I have a simple class like this (with rank, for you):
public class MyTreeClass
{
public string Name { get; set; }
public int Rank { get; set; }
}
I've added rachel's class as is:
public class SortableObservableCollection<T> : ObservableCollection<T> { ...}
Property for the binding:
public SortableObservableCollection<MyTreeClass> MyTree
{
get { return _myTree; }
set { _myTree = value; }
}
private SortableObservableCollection<MyTreeClass> _myTree;
Command for the action:
public ICommand SortMe_Command { get; set; }
In the constructor:
MyTree = new SortableObservableCollection<MyTreeClass>() {new MyTreeClass(){Name = "One",Rank = 1},
new MyTreeClass(){Name = "Two",Rank = 2},
new MyTreeClass(){Name = "Three",Rank = 3}};
SortMe_Command = new RelayCommand<object>(Execute_SortMe);
SortMe_Command = new RelayCommand<object>(Execute_SortMe);
And last but not least, the execute method:
private void Execute_SortMe(object obj)
{
MyTree[0].Rank = 5;
MyTree[1].Rank = 4;
MyTree.Sort(node => node.Rank);
}
Now when I click the button, it will change 2 item's rank, and resort the tree according to rank.
In your application, just figure out which one's you're swapping, and you're set ...

MVVM DataBinding

I've started an MVVM project and now I'm stucking with correct DataBinding.
My project has:
A UserControl whit a ViewModel as DataContext like:
public partial class TestUserControl: UserControl
{
public TestUserControl()
{
this.DataContext = new TestUserControlViewModel();
}
}
ViewModel code is (BaseViewModel class contains PropertyChangedEventHandler):
public class TestUserControlViewModel : BaseViewModel
{
public KrankenkasseControlViewModel()
{}
public IEnumerable<DataItem> GetAllData
{
get
{
IGetTheData src= new DataRepository();
return src.GetData();
}
}
}
IGetTheData is the interface to DataContext:
public interface IGetTheData
{
IEnumerable<DataItem> GetData();
}
}
and finally the DataRepository code:
public class DataRepository : IGetTheData
{
private TestProjectDataContext dax = new TestProjectDataContext();
public IEnumerable<DataItem> GetData()
{
return (from d in this.dax.TestData
select new DataItem
{
ID = d.ID,
SomeOtherData = d.SomeOtherData
});
}
}
My UserControl has a few TextBoxes, but what's the best way to bind correctly?
Thanks for your help, regards.
EDIT: Binding the data against multiple textboxes
After reading your comment, I will elaborate my example for textboxes.
First important thing is that the ViewModel will model the things in the View, so that the View gets all information it needs in the structure it needs. That means, if you have multiple textboses in the View, you will need multiple string Properties in your ViewModel, one for each textbox.
In your XAML you could have something like
<TextBox Text="{Binding ID, Mode=TwoWay}" />
<TextBox Text="{Binding SomeOtherData, Mode=TwoWay}" />
and in your ViewModel
public class TestUserControlViewModel : BaseViewModel {
private string id;
private string someOtherData;
public TestUserControlViewModel() {
DataItem firstItem = new DataRepository().GetData().First();
this.ID = firstItem.ID;
this.SomeOtherData = firstItem.SomeOtherData;
}
public string ID {
get {
return this.id;
}
set {
if (this.id == value) return;
this.id = value;
this.OnPropertyChangedEvent("ID");
}
}
public string SomeOtherData {
get {
return this.someOtherData;
}
set {
if (this.someOtherData == value) return;
this.someOtherData = value;
this.OnPropertyChangedEvent("SomeOtherData");
}
}
}
Here I assume that in your BaseViewModel there is an OnPropertyChangedEvent method to fire the corresponding event. This tells the View that the property has changed and it must update itself.
Note the Mode=TwoWay in the XAML. This means, that it doesn't matter on which side the value changes, the other side will reflect the change immediately. So if the user changes a value in a TwoWay bound TextBox, then the corresponding ViewModel property will automatically change! And also vice versa: if you change the ViewModel property programmatically, the View will refresh.
If you want to show multiple textboxes for more than one data item, then you must introduce more Properties in the ViewModel and bind them accordingly. Maybe a ListBox with a flexible number of TextBoxes inside is a solution then, like #Haspemulator already answered.
Binding the data against a collection control
In the TestUserControl I guess you have a control (like a ListView) to show the list of loaded things. So bind that control against the list in the ViewModel with
<ListView ... ItemsSource="{Binding GetAllData}" ... />
First you must understand that Binding means not "read the data and then forget the ViewModel". Instead you bind the View to the ViewModel (and its Properties) as long as the View lasts. From this point of view, AllData is a much better name than GetAllData (thanks #Malcolm O'Hare).
Now in your code, every time the View reads the AllData property, a new DataRepository is created. Because of the Binding, that is not what you want, instead you want to have one instance of DataRepository for the whole lifetime of the View, which is used to read the initial data and can later be used to update the View, if the underlying database changes (maybe with an event).
To enable such a behavior you should change the type of the AllData property to an ObservableCollection, so that the View can automatically update the list if changes occur.
public class TestUserControlViewModel : BaseViewModel
private ObservableCollection<DataItem> allData;
public TestUserControlViewModel() {
IGetTheData src = new DataRepository();
this.allData = new ObservableCollection<DataItem>(src.GetData());
}
public ObservableCollection<DataItem> AllData {
get {
return this.allData;
}
}
public void AddDataItem(DataItem item) {
this.allData.Add(item);
}
}
Now if you call AddDataItem later, the ListView will update itself automatically.
Your Property Name is bad. You should call it AllData, not GetAllData.
Since you are returning a collection, you probably should be using some sort of list control (ListBox, ListView).
In that case you'd be doing
<ListBox ItemsSource="{Binding GetAllData}" />
Guten Abend. :) As it already mentioned, since you're returning the collection, it's better to use a ListBox. The comment about having ObservableCollection as a cache is also absolutely valid. I would add that if you need to have your data editable, you should use TextBox inside the ItemTemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text={Binding SomeOtherData,Mode=TwoWay} />
</DataTemplate>
</ListBox.ItemTemplate>
In this case if user edits the text in the box, data will be updated in your data object, so that it could be saved in the database later.

Create a simple DataGridView with List of objects and checkbox column (C#)

I have looked at a lot of places and I'm struggling to do this supposedly simple thing. I have a Windows form on which I've to show a simple DataGridView in this form:
| (CheckBoxColumn) | FilePath | FileState |
Now, there are a couple of problems. The data I need to bind to them is in a List of objects like this:
class FileObject
{
string filePath;
string fileState;
}
//Now here's the list of these objects which I populate somehow.
List<FileObject> listFiles;
Is there any efficient way to bind this directly to the DataGridView
so that different members of Object in the list are bound to
different columns, and for each there's checkbox?
I saw the examples of adding checkbox column to a datagrid, but none of them showed how
to add it to the 'header' row as well, so that it can behave as a 'check all'/'uncheck all' box.
Any help in how to achieve this would be great! I do write simple applications in C# but never had to work with datagrids etc. :(
Thanks!
The DataGridView control has a feature to automatically generate columns that can be set by the AutoGenerateColumns property. This property defaults to true - that is columns are by default auto generated.
However, columns are only automatically generated for public properties of the object you bind to the grid - fields do not show up.
Auto generation also works for check box columns when there is a public boolean property on the bound object.
So the simplest way to achieve your first two requirements is to change your FileObject class to this:
public class FileObject
{
public string FilePath { get; set; }
public string FileState { get; set; }
public bool Selected { get; set; }
}
If you cannot modify that class then next best would be the create a wrapper object that holds a file object:
public class FileObjectWrapper
{
private FileObject fileObject_;
FileObjectWrapper()
{
fileObject_ = new FileObject();
}
FileObjectWrapper(FileObject fo)
{
fileObject_ = fo;
}
public string FilePath
{
get { return fileObject_.filePath; }
set { fileObject_.filePath = value; }
}
public string FileState
{
get { return fileObject_.fileState; }
set { fileObject_.fileState= value; }
}
public bool Selected { get; set; }
}
You can then create your list to bind to (a BindingList is usually best) doing something like:
var fowList = new BindingList<FileObjectWrapper>();
foreach (FileObject fo in // here you have your list of file objects! )
{
fowList.Add(new FileObjectWrapper(fo));
}
dataGridView1.DataSource = fowList;
There are many ways to do the above but that is a general idea.
You can also add an unbound DataGridViewCheckBoxColumn to the grid, though I find it easier to have in the the bound list. Here is how if you do need to:
DataGridViewCheckBoxColumn c = new DataGridViewCheckBoxColumn();
c.Name = "Selected";
dataGridView1.Columns.Add(c);
Finally, for having a "SelectedAll" option in the header you will need to use custom code.
The article on CodeProject that Umesh linked to (CheckBox Header Column for DataGridView) looks quite easy to implement - they create a custom DataGridViewHeaderCell overriding the Paint and OnMouseClick methods.
Please refer the the below example, showing exactly what you are looking for
http://www.codeproject.com/Articles/20165/CheckBox-Header-Column-For-DataGridView

Categories

Resources