Hello Thanks for you Help...
I have a WPF application that I am trying to Create a Combobox in and pass the combobox a list of strings.
Below is my XAML code with a is inside DataGrid.Columns
<DataGridTextColumn Header="Mapping Notes" Binding="{Binding Path=MappingNote}" />
<DataGridComboBoxColumn x:Name="functionArg1Combobox" Header="Arg1" ItemsSource="{Binding ArgumentDropDownList}" SelectedItemBinding="{Binding Path=arg1}" />
In F# I have Type FDMappingRow under that I create a member
type FDMappingRow() =
let mutable targetField: string = "initial target"
let mutable speacialFunction: string = "innitial function"
let mutable mappingNote: string = "ATesting"
let mutable argumentDropDownList: System.Collections.Generic.List<string> = new List<string>()
member x.MappingNote
with get() = mappingNote
and set(t) =
mappingNote <- t
x.TriggerPropertyChanged "MappingNote"
//Testing to see if I can get Combobox working
member x.Argument1
with get() = argumentDropDownList
and set(t) =
argumentDropDownList <- t
x.TriggerPropertyChanged "and pass it that list into to populate the combobox"
Basically my question is how do I take the statement below and pass it this
"let mutable argumentDropDownList: System.Collections.Generic.List = new"
so I can populate the ComboBox?
member x.Argument1
with get() = argumentDropDownList
and set(t) =
argumentDropDownList <- t
x.TriggerPropertyChanged "and pass it that list into to populate the combobox"
What is the correct syntax?
This would help me a lot. I can get Mapping Notes working correctly cause it just a txt box but cant figure out for DataGridComboBoxColumn
Related
Here's code to populate a combobox with Linq-to-SQL:
private void FillEmbCB()
{
DataClasses1DataContext dc = new DataClasses1DataContext();
var emb = from e in dc.EMBALLAGES
select new
{
e.EMB_CODE,
e.EMB_LIB
};
Emb1CB.ItemsSource = emb;
Emb1CB.SelectedValuePath = "EMB_CODE";
Emb1CB.DisplayMemberPath = "EMB_LIB";
Emb1CB.SelectedItem = "HOUSSE PROTEC PALETTE"; // Nothing appears in the combobox
dc.Dispose();
}
At the end of that code, I want to choose an item from the combobox I've just populated, and show it in the combobox, but nothing appears, the combobox is always empty.
Here's my Xaml markup:
<ComboBox x:Name="Emb1CB" Width="200" FontSize="12"
SelectionChanged="Emb1CB_SelectionChanged"/>
SelectedValuePath is used for data binding (which you should be doing instead of manipulating the GUI directly like this, but that's another discussion). If you want to set SelectedItem yourself then you need to set the value to the parent object that contains the value, e.g.:
using System.Linq;
Emb1CB.SelectedItem = emb.FirstOrDefault(x => x.EMB_CODE == "HOUSSE PROTEC PALETTE");
From my ViewModel, I need to programmatically move the focus and highlight of a row in a WPF DataGrid. The DataGrid has just one column:
<DataGrid Name="DgAdrType"
ItemsSource="{Binding ItemsLcv}"
IsSynchronizedWithCurrentItem="True"
<DataGridTextColumn Header=" Description"
IsReadOnly="True"
CanUserSort="True" Binding="{Binding descr, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
And in the datacontext ViewModel:
private IEnumerable<AdrTypeMdl> _itemsList;
ItemsLcv = CollectionViewSource.GetDefaultView(_itemsList) as ListCollectionView;
This works even though I don't have a property per se in the ViewModel for the data field "descr", because I bind the DataGrid's ItemSource.
In the ViewModel I can access the View DataGrid's ItemCollection of items by passing in that ItemCollection from the View like so:
<!-- Interaction for click selection -->
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotMouseCapture">
<i:InvokeCommandAction Command="{Binding SelObjChangedCommand}"
CommandParameter="{Binding ElementName=DgAdrType, Path=Items}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
And back in the ViewModel, I load the DataGrid items like so:
private ItemCollection _dgItems;
private void SelObjChanged(object theItems)
{if (theItems !=null)
{ _dgItems = theItems as ItemCollection;
I want to keep the cast to ItemCollection so that I can retain the DataGrid properties of that ItemCollection. The problem is the ItemCollection's IndexOf method is not working. I only get -1 when I try to find the index of one of the class object items by doing this.
var idx = _dgItems.IndexOf(myobject);
EDIT ------- this is entire code of the method try IndesOf
private void HandleUpdateListEvent(Object myobject)
{AdrTypeMdl theNewItem = myobject as AdrTypeMdl;
bool co = _dgItems.Contains(theNewItem);
var idx = _dgItems.IndexOf(theNewItem);
_dgItems.MoveCurrentToPosition(idx);
_dgItems.Refresh();}
EDIT ---------------------------------
This is the easier approach but I still need help with the lambda / filter expression and method call
// this is where I try to get the index of an object for highlighting
private void HandleUpdateListEvent(Object myobject)
AdrTypeMdl theNewItem = myobject as AdrTypeMdl;
var e = ItemsLcv.SourceCollection.GetEnumerator();
ItemsLcv.Filter = o => (o == theNewItem);
foreach (row in ItemsLcv)
{ if row == theNewItem
return e >;
e = -1;}
ItemsLcv.MoveCurrentToPosition(e);
ItemsLcv.Refresh();}
END EDIT ---------------------
In debugger I can see the class Objects in _dgItems. If I do this, it works.
var idx = _dgItems.IndexOf(_dgItems[2]);
But the IndexOf method does not work when the parameter is just a class Object. I think the problem is with my cast of the DataGrid items to an ItemCollection. I need to cast the class Object, ie. myobject, to something recognizable by the ItemCollection that I got from the DataGrid. Is there a workaround? Thank you.
Try this.
You need to cast it to the type of collection ie AdrTypeMdl. You cannot simply get the index by passing an object. You're binding to a source ItemsLcv which is of type AdrTypeMd1. So pass that exact type to get the exact index.
var dgcolumn = myobject as AdrTypeMdl;
if(dgcolumn != null)
{
var idx = _dgItems.IndexOf(dgcolumn);
}
idx will be the index of that corresponding column.
I have a DataGrid (dataGrid1) where records can be added and deleted.
Based on that dataGrid1, I want to make a new Grid with buttons in it based on ID and Types'. Cols will also have to given a DataSource of add dynamically, but that will be just while generating for the 1st time in Window_Loaded itself. Rows can be added/removed based on changes in dataGrid1. I want somethign like this :
On each Btn click, a new window will be opened for entry of the particular Type and for the particular ID. If the details are already entered, then the text of btn wil be "Update" else "Add".
What could be the best resource/control to perform this operations ? At present, I just did a Grid with 2 stable cols. Any ideas for the above to use Grid, DataGrid or something else. And adding/removing rows will be easy in which way and how.
Any help is appreciated.
Okay, let me try to take an example which is similar to your needs
Let's assume we use this class:
public class MyObject
{
public int MyID;
public string MyString;
public ICommand MyCommand;
}
And we are willing to display a DataGrid listing the ID, and having as a second column a Button, with the property MyString as content, which, when clicked, launches the ICommand MyCommand which opens in a new window whatever you want.
Here is what you should have on the View side:
<DataGrid ItemsSource="{Binding MyList}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding MyID}" />
<DataGridTemplateColumn Header="Buttons">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="{Binding MyString}" Command="{Binding MyCommand}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
This will show a DataGrid taking all the content in an IEnumerable<MyObject> named 'MyList', and shows two columns as defined before.
Now if you need to define the command.
First, I recommend you read this introductory link to MVVM and take the RelayCommand class (that's what we're gonna use for your problem)
So, in your ViewModel, the one which defines the MyList, here is how you should define some of the useful objects:
public ObservableCollection<MyObject> MyList { get; set; }
// blah blah blah
public void InitializeMyList()
{
MyList = new ObservableCollection<MyObject>();
for (int i = 0; i < 5; i++)
{
MyList.Add(InitializeMyObject(i));
}
}
public MyObject InitializeMyObject(int i)
{
MyObject theObject = new MyObject();
theObject.MyID = i;
theObject.MyString = "The object " + i;
theObject.MyCommand = new RelayCommand(param =< this.ShowWindow(i));
return theObject
}
private void ShowWindow(int i)
{
// Just as an exammple, here I just show a MessageBox
MessageBox.Show("You clicked on object " + i + "!!!");
}
This should be enough to create whatever you want. As you can see, every Button will call a method (ShowWindow) which is defined to show your new window, do whatever you need inside. The RelayCommand is actually just here, as its name says, to relay the command fired by the button to a method which contains the execution logic.
And... I think that's all you need. Sorry for the late answer BTW
EDIT - generating columns manually/dynamically
The following code is part of a code I had to do when I had a similar problem.
My problem was, I needed to change the columns displayed every time a ComboBox's SelectedItem would change. So I put this in a SelectionChanged event handler.
I don't know where exactly do you need to generate your columns, but I'll give you a general example.
Assume your ItemsSource is an ObservableCollection<MyNewObject>
MyNewObject is the following:
public class MyNewObject
{
public IList<string> MyStrings { get; set; }
}
You should put somewhere in your code (should be when you need to generate the column) the following code, which is generating a number of columns equal to the length of the first MyNewObject from the list (note: this is in code-behind, and the DataGrid you're working on is named dataGrid)
ObservableCollection<MyNewObject> source = dataGrid.ItemsSource as ObservableCollection<MyNewObject>;
if (source == null || source.Count == 0)
{
return;
}
MyNewObject firstObject = source[0];
for(int i = 0; i < firstObject.MyStrings.Count; i++)
{
// Creates one column filled with buttons for each string
DataGridTemplateColumn columnToAdd = new DataGridTemplateColumn();
columnToAdd.Width = 110; // I set a manual width, but you can do whatever you want
columnToAdd.Header = "Header number " + i;
// Create the template with a Button inside, bound to the appropriate string
DataTemplate dataTemplate = new DataTemplate(typeof(Button));
FrameworkElementFactory buttonElement = new FrameworkElementFactory(typeof(Button));
Binding binding = new Binding("MyStrings[" + i + "]");
binding.Mode = BindingMode.TwoWay;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
buttonElement.SetBinding(Button.ContentProperty, binding);
// Do the same here for your command, or for whatever you want to do when the user clicks on this button
dataTemplate.VisualTree = buttonElement;
columnToAdd.CellTemplate = dataTemplate;
dataGrid.Columns.Add(columnToAdd);
}
This will create one column for each string found in the first object. Then, enhance it with whatever command or display trick you need!
Here is my code for binding a dictionary item to a Chart control. I keep getting the following error:
"Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index"
Here is my Code:
foreach (DataRow drow in objD0s.Tables[0].Rows)// Adding values from a dataset to dictionary
{
string strvalue = Convert.ToString(drow["Chemical Name"]);
string doublevalue = Convert.ToString(drow["Global Warming"]);
Cdata.Add(Convert.ToString(strvalue), Convert.ToDouble(doublevalue));
}
ColumnSeries colSeries = ChartChemImpact.Series[0] as ColumnSeries;
((ColumnSeries)ChartChemImpact.Series[0]).DataContext = Cdata;
colSeries.ItemsSource = myDataTable0.DefaultView;
colSeries.IndependentValueBinding = new Binding("[Chemical Name]");
colSeries.DependentValueBinding = new Binding("[Global Warming]");
}
XAM
chartingToolkit:Chart Name="ChartChemImpact" Title="Chart Title" Width="384" Height="280">
<chartingToolkit:ColumnSeries DependentValuePath="Key" IndependentValuePath="Value" ItemsSource="{Binding}" Name="colSeries" />
</chartingToolkit:Chart>
Please help :(
Ok, I have found the mistake. There are binding and value paths in the xaml, but they are replaced in the code.
I would remove those lines, so here is the result:
foreach (DataRow drow in objD0s.Tables[0].Rows)// Adding values from a dataset to dictionary
{
string strvalue = Convert.ToString(drow["Chemical Name"]);
string doublevalue = Convert.ToString(drow["Global Warming"]);
Cdata.Add(Convert.ToString(strvalue), Convert.ToDouble(doublevalue));
}
((ColumnSeries)ChartChemImpact.Series[0]).DataContext = Cdata;
//And that's the end of the function, no more code
}
I want to use the GridView mode of a ListView to display a set of data that my program will be receiving from an external source. The data will consist of two arrays, one of column names and one of strings values to populate the control.
I don't see how to create a suitable class that I can use as the Item in a ListView. The only way I know to populate the Items is to set it to a class with properties that represent the columns, but I have no knowledge of the columns before run-time.
I could create an ItemTemplate dynamically as described in: Create WPF ItemTemplate DYNAMICALLY at runtime but it still leaves me at a loss as to how to describe the actual data.
Any help gratefully received.
You can add GridViewColumns to the GridView dynamically given the first array using a method like this:
private void AddColumns(GridView gv, string[] columnNames)
{
for (int i = 0; i < columnNames.Length; i++)
{
gv.Columns.Add(new GridViewColumn
{
Header = columnNames[i],
DisplayMemberBinding = new Binding(String.Format("[{0}]", i))
});
}
}
I assume the second array containing the values will be of ROWS * COLUMNS length. In that case, your items can be string arrays of length COLUMNS. You can use Array.Copy or LINQ to split up the array. The principle is demonstrated here:
<Grid>
<Grid.Resources>
<x:Array x:Key="data" Type="{x:Type sys:String[]}">
<x:Array Type="{x:Type sys:String}">
<sys:String>a</sys:String>
<sys:String>b</sys:String>
<sys:String>c</sys:String>
</x:Array>
<x:Array Type="{x:Type sys:String}">
<sys:String>do</sys:String>
<sys:String>re</sys:String>
<sys:String>mi</sys:String>
</x:Array>
</x:Array>
</Grid.Resources>
<ListView ItemsSource="{StaticResource data}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Path=[0]}" Header="column1"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=[1]}" Header="column2"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=[2]}" Header="column3"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
Thanks, that is very helpful.
I used it to create a dynamic version as follows. I created the column headings as you suggested:
private void AddColumns(List<String> myColumns)
{
GridView viewLayout = new GridView();
for (int i = 0; i < myColumns.Count; i++)
{
viewLayout.Columns.Add(new GridViewColumn
{
Header = myColumns[i],
DisplayMemberBinding = new Binding(String.Format("[{0}]", i))
});
}
myListview.View = viewLayout;
}
Set up the ListView very simply in XAML:
<ListView Name="myListview" DockPanel.Dock="Left"/>
Created an wrapper class for ObservableCollection to hold my data:
public class MyCollection : ObservableCollection<List<String>>
{
public MyCollection()
: base()
{
}
}
And bound my ListView to it:
results = new MyCollection();
Binding binding = new Binding();
binding.Source = results;
myListview.SetBinding(ListView.ItemsSourceProperty, binding);
Then to populate it, it was just a case of clearing out any old data and adding the new:
results.Clear();
List<String> details = new List<string>();
for (int ii=0; ii < externalDataCollection.Length; ii++)
{
details.Add(externalDataCollection[ii]);
}
results.Add(details);
There are probably neater ways of doing it, but this is very useful for my application. Thanks again.
This article on the CodeProject explains exactly how to create a Dynamic ListView - when data is known only at runtime.
http://www.codeproject.com/KB/WPF/WPF_DynamicListView.aspx
Not sure if it's still relevant but I found a way to style the individual cells with a celltemplate selector. It's a bit hacky because you have to munch around with the Content of the ContentPresenter to get the proper DataContext for the cell (so you can bind to the actual cell item in the cell template):
public class DataMatrixCellTemplateSelectorWrapper : DataTemplateSelector
{
private readonly DataTemplateSelector _ActualSelector;
private readonly string _ColumnName;
private Dictionary<string, object> _OriginalRow;
public DataMatrixCellTemplateSelectorWrapper(DataTemplateSelector actualSelector, string columnName)
{
_ActualSelector = actualSelector;
_ColumnName = columnName;
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
// The item is basically the Content of the ContentPresenter.
// In the DataMatrix binding case that is the dictionary containing the cell objects.
// In order to be able to select a template based on the actual cell object and also
// be able to bind to that object within the template we need to set the DataContext
// of the template to the actual cell object. However after the template is selected
// the ContentPresenter will set the DataContext of the template to the presenters
// content.
// So in order to achieve what we want, we remember the original DataContext and then
// change the ContentPresenter content to the actual cell object.
// Therefor we need to remember the orginal DataContext otherwise in subsequent calls
// we would get the first cell object.
// remember old data context
if (item is Dictionary<string, object>)
{
_OriginalRow = item as Dictionary<string, object>;
}
if (_OriginalRow == null)
return null;
// get the actual cell object
var obj = _OriginalRow[_ColumnName];
// select the template based on the cell object
var template = _ActualSelector.SelectTemplate(obj, container);
// find the presenter and change the content to the cell object so that it will become
// the data context of the template
var presenter = WpfUtils.GetFirstParentForChild<ContentPresenter>(container);
if (presenter != null)
{
presenter.Content = obj;
}
return template;
}
}
Note: I changed the DataMatrix frome the CodeProject article so that rows are Dictionaries (ColumnName -> Cell Object).
I can't guarantee that this solution will not break something or will not break in future .Net release. It relies on the fact that the ContentPresenter sets the DataContext after it selected the template to it's own Content. (Reflector helps a lot in these cases :))
When creating the GridColumns, I do something like that:
var column = new GridViewColumn
{
Header = col.Name,
HeaderTemplate = gridView.ColumnHeaderTemplate
};
if (listView.CellTemplateSelector != null)
{
column.CellTemplateSelector = new DataMatrixCellTemplateSelectorWrapper(listView.CellTemplateSelector, col.Name);
}
else
{
column.DisplayMemberBinding = new Binding(string.Format("[{0}]", col.Name));
}
gridView.Columns.Add(column);
Note: I have extended ListView so that it has a CellTemplateSelector property you can bind to in xaml
#Edit 15/03/2011:
I wrote a little article which has a little demo project attached: http://codesilence.wordpress.com/2011/03/15/listview-with-dynamic-columns/
Totally progmatic version:
var view = grid.View as GridView;
view.Columns.Clear();
int count=0;
foreach (var column in ViewModel.GridData.Columns)
{
//Create Column
var nc = new GridViewColumn();
nc.Header = column.Field;
nc.Width = column.Width;
//Create template
nc.CellTemplate = new DataTemplate();
var factory = new FrameworkElementFactory(typeof(System.Windows.Controls.Border));
var tbf = new FrameworkElementFactory(typeof(System.Windows.Controls.TextBlock));
factory.AppendChild(tbf);
factory.SetValue(System.Windows.Controls.Border.BorderThicknessProperty, new Thickness(0,0,1,1));
factory.SetValue(System.Windows.Controls.Border.MarginProperty, new Thickness(-7,0,-7,0));
factory.SetValue(System.Windows.Controls.Border.BorderBrushProperty, Brushes.LightGray);
tbf.SetValue(System.Windows.Controls.TextBlock.MarginProperty, new Thickness(6,2,6,2));
tbf.SetValue(System.Windows.Controls.TextBlock.HorizontalAlignmentProperty, column.Alignment);
//Bind field
tbf.SetBinding(System.Windows.Controls.TextBlock.TextProperty, new Binding(){Converter = new GridCellConverter(), ConverterParameter=column.BindingField});
nc.CellTemplate.VisualTree = factory;
view.Columns.Add(nc);
count++;
}
I would go about doing this by adding an AttachedProperty to the GridView where my MVVM application can specify the columns (and perhaps some additional metadata.) The Behavior code can then dynamically work directly with the GridView object to create the columns. In this way you adhere to MVVM and the ViewModel can specify the columns on the fly.