I have a WPF ListBox of Grids that I create as follows:
I define the listbox in XAML with a simple declaration as follows:
<ListBox Name="MyListbox" >
</ListBox>
In code, I dynamically create an arbitrary number of Grid items (System.Windows.Controls.Grid), and I add them to the ListBox. Something like:
foreach (MyDataType myItem in MyDataList)
{
Grid newGrid = new Grid();
// Code that sets the properties and values of the grid, based on myItem
MyListbox.Items.Add(newGrid);
}
This works great, and everything looks the way that I want it to.
Now, I've decided that in my ListBox, I also want to store a reference to the actual myItem object, so that I can reference it later.
My idea was to create a new class like:
public class ListGridItemNode
{
public MyDataType theItem;
public Grid theGrid;
public ListGridItemNode(MyDataType inItem, Grid inGrid)
{
theItem = inItem;
theGrid = inGrid;
}
}
And then change my code to:
foreach (MyDataType myItem in MyDataList)
{
Grid newGrid = new Grid();
// Code that sets the properties and values of the grid, based on myItem
MyListbox.Items.Add(new ListGridItemNode(myItem,newGrid));
}
Of course, now my listbox instead of displaying the grids, just displays the text "MyApp.ListGridItemNode".
My question is: How do I tell the ListBox to go a level deeper and display the actual Grids inside of each ListGridItemNode object?
I suspect that this has something to do with bindings, but I can't find any examples that work the way that I'm doing it. Most of what I'm finding only shows binding to a string within an object, not an entire control.
Couldn't you just use the Tag property of the Grid object?
newGrid.Tag = myItem;
Then later:
Grid grid; // obtain Grid object somehow
MyItem myItem = (MyItem) grid.Tag;
Related
I'm trying to get into Xamarin development and followed Microsofts video tutorial
https://www.youtube.com/watch?v=OPXVqdRXZms&list=PLdo4fOcmZ0oU10SXt2W58pu2L0v2dOW-1&index=9
Currently I would like to populate my ListView with some basic labels. So first the following code works fine for me. I created a custom ViewCell and assign it to the ListView as the ItemTemplate
public class MasterPage : ContentPage
{
public ListView MasterPageNavigationItemsView { get; }
public MasterPage()
{
// ...
MasterPageNavigationItemsView = new ListView()
{
ItemTemplate = new DataTemplate(() => new MasterPageItemViewCell()),
SeparatorVisibility = SeparatorVisibility.None
};
MasterPageNavigationItemsView.SetBinding(ListView.ItemsSourceProperty, nameof(MasterViewModel.MasterPageItemsCollection));
// ...
}
}
internal class MasterPageItemViewCell : ViewCell
{
public MasterPageItemViewCell()
{
Label label = new Label();
label.SetBinding(Label.TextProperty, nameof(MasterPageItem.Title));
View = label;
}
}
I would prefer to create a custom DataTemplate as they did in the video tutorial. I found the code on Github
https://github.com/codemillmatt/xamarin-101/blob/8271814c7ebdd41387e20ed33b3dfbdcd54409be/coded-ui-navigation/CodedUINav/Views/MainPage.cs#L88-L112
So instead of doing ItemTemplate = new DataTemplate(() => new MasterPageItemViewCell()), I would like to do ItemTemplate = new MasterPageItemTemplate(),
which results in the class
internal class MasterPageItemTemplate : DataTemplate
{
public MasterPageItemTemplate() : base(LoadTemplate)
{
}
private static Label LoadTemplate()
{
Label titleLabel = new Label();
titleLabel.SetBinding(Label.TextProperty, nameof(MasterPageItem.Title));
return titleLabel;
}
}
So I took the code from Github and modified it a little bit. When I run the code the labels content is empty and when I click on it the application crashes.
How can I fix the MasterPageItemTemplate?
Update
I found another sample that makes use of view cells
https://github.com/xamarin/xamarin-forms-samples/tree/master/WorkingWithListview/WorkingWithListview/Custom
so I think I should follow this and use this code for now
ItemTemplate = new DataTemplate(typeof(MasterPageItemViewCell)),
Data items in a ListView are called cells. Each cell corresponds to a row of data. There are built-in cells to choose from, or you can define your own custom cell. Both built-in and custom cells can be used/defined in XAML or code.The child of an inline DataTemplate must be of, or derive from, type Cell.Here in your first link sample, the ListView.ItemTemplate property is set to a DataTemplate that's created from a custom type that defines the cell appearance. The custom type derive from type ViewCell,but in your codes custom DataTemplate return a Label,it will not work.
you could refer to Creating DataTemplate
And in the sample of your second link,it use CollectionView.You could nest markup inside a DataTemplate tag to create a View.
Note :When using CollectionView, never set the root element of your DataTemplate objects to a ViewCell. This will result in an exception being thrown because CollectionView has no concept of cells.
I have a form that has a dynamic amount of datagrids that are brought in programmatically each one on a new tabpage.
My problem is that I need to change the Header of each column. I have tried doing it through a method
DataGridForSupplier.Columns[0].Header = "123";
but that keeps crashing with an error:
Index was out of range. Must be non-negative and less than the size of the collection
Turns out the problem is that the grid wasn't finished loading. So after waiting for all tabpage to load and add data to all the grids , even then the code
DataGridForSupplier.Columns[0].Header = "123";
would still crash. If the tabs are left to load on their own with no header tampering then the datagrid shows fine.
I would just LOVE to do this in XAML problem is that seeing that I don't know how many grids will load at run time I tried doing this at the back. So I'm open to any solution at this point. I tried finding a solution that would incorporate something that would 'theme' all the datagrids. Luckily all the datagrids headers will repeat across all tabs. So header 1 on tabpage 1 - 10 will be the same. Header 2 on tabpage 1 - 10 will be the same
Something like
<DataGridTemplateColumn.Header>
<TextBlock Text="{Binding DataContext.HeaderNameText, RelativeSource=>> RelativeSource AncestorType={x:Type DataGrid}}}" />
</DataGridTemplateColumn.Header>
but this needs to repeat for every Grid. This seems to escape me at the moment.
Any help would be welcome.
A rather lengthy answer, but this solution does not require any additional libraries, 3rd party tools, etc. You can expand it as you want later such as for adding hooks to mouse-move/over/drag/drop/focus, etc. First the premise on subclassing which I found out early in my learning WPF. You can not subclass a xaml file, but can by a .cs code file. In this case, I subclassed the DataGrid to MyDataGrid. Next, I created an interface for a known control type to ensure contact of given functions/methods/properties. I have stripped this version down to cover just what you need to get.
The interface below is just to expose any class using this interface MUST HAVE A METHOD called MyDataGridItemsChanged and expects a parameter of MyDataGrid.. easy enough
public interface IMyDataGridSource
{
void MyDataGridItemsChanged(MyDataGrid mdg);
}
Now, declaring in-code a MyDataGrid derived from DataGrid. In this class, I am adding a private property of type IMyDataGridSource to grab at run-time after datagrids are built and bound.
public class MyDataGrid : DataGrid
{
// place-holder to keep if so needed to expand later
IMyDataGridSource boundToObject;
public MyDataGrid()
{
// Force this class to trigger itself after the control is completely loaded,
// bound to whatever control and is ready to go
Loaded += MyDataGrid_Loaded;
}
private void MyDataGrid_Loaded(object sender, RoutedEventArgs e)
{
// when the datacontext binding is assigned or updated, see if it is based on
// the IMyDataGridSource object. If so, try to type-cast it and save into the private property
// in case you want to add other hooks to it directly, such as mouseClick, grid row changed, etc...
boundToObject = DataContext as IMyDataGridSource;
}
// OVERRIDE the DataGrid base class when items changed and the ItemsSource
// list/binding has been updated with a new set of records
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
// do whatever default behavior
base.OnItemsChanged(e);
// if the list is NOT bound to the data context of the IMyDataGridSource, get out
if (boundToObject == null)
return;
// the bound data context IS of expected type... call method to rebuild column headers
// since the "boundToObject" is known to be of IMyDataGridSource,
// we KNOW it has the method... Call it and pass this (MyDataGrid) to it
boundToObject.MyDataGridItemsChanged(this);
}
}
Next into your form where you put the data grid. You will need to add an "xmlns" reference to your project so you can add a "MyDataGrid" instead of just "DataGrid". In my case, my application is called "StackHelp" as this is where I do a variety of tests from other answers offered. The "xmlns:myApp" is just making an ALIAS "myApp" to the designer to it has access to the classes within my application. Then, I can add
<Window x:Class="StackHelp.MyMainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myApp="clr-namespace:StackHelp"
Title="Main Window" Height="700" Width="900">
<StackPanel>
<!-- adding button to the main window to show forced updated list only -->
<Button Content="Refresh Data" Width="100"
HorizontalAlignment="Left" Click="Button_Click" />
<myApp:MyDataGrid
ItemsSource="{Binding ItemsCollection, NotifyOnSourceUpdated=True}"
AutoGenerateColumns="True" />
</StackPanel>
</Window>
Now, into the MyMainWindow.cs code-behind
namespace StackHelp
{
public partial class MyMainWindow : Window
{
// you would have your own view model that all bindings really go to
MyViewModel VM;
public MyMainWindow()
{
// Create instance of the view model and set the window binding
// to this public object's DataContext
VM = new MyViewModel();
DataContext = VM;
// Now, draw the window and controls
InitializeComponent();
}
// for the form button, just to force a refresh of the data.
// you would obviously have your own method of querying data and refreshing.
// I am not obviously doing that, but you have your own way to do it.
private void Button_Click(object sender, RoutedEventArgs e)
{
// call my viewmodel object to refresh the data from whatever
// data origin .. sql, text, import, whatever
VM.Button_Refresh();
}
}
}
Finally to my sample ViewModel which incorporates the IMyDataGridSource
public class MyViewModel : IMyDataGridSource, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
public ObservableCollection<OneItem> ItemsCollection { get; set; }
= new ObservableCollection<OneItem>();
public void Button_Refresh()
{
ItemsCollection = new ObservableCollection<OneItem>
{
new OneItem{ DayName = "Sunday", DayOfWeek = 0},
new OneItem{ DayName = "Monday", DayOfWeek = 1},
new OneItem{ DayName = "Tuesday", DayOfWeek = 2},
new OneItem{ DayName = "Wednesday", DayOfWeek = 3},
new OneItem{ DayName = "Thursday", DayOfWeek = 4},
new OneItem{ DayName = "Friday", DayOfWeek = 5 },
new OneItem{ DayName = "Saturday", DayOfWeek = 6 }
};
RaisePropertyChanged("ItemsCollection");
}
// THIS is the magic hook exposed that will allow you to rebuild your
// grid column headers
public void MyDataGridItemsChanged(MyDataGrid mdg)
{
// if null or no column count, get out.
// column count will get set to zero if no previously set grid
// OR when the items grid is cleared out. don't crash if no columns
if (mdg == null)
return;
mdg.Columns[0].Header = "123";
}
}
Now, taking this a step further. I don't know how you manage your view models and you may have multiple grids in your forms and such. You could create the above MyViewModel class as a smaller subset such as MyDataGridManager class. So each datagrid is bound to its own MyDataGridManager instance. It has its own querying / populating list for the grid, handling its own rebuild column headers, mouse clicks (if you wanted to expand), record change selected, etc.
Hope this helps you some. Again, this does not require any other 3rd party libraries and you can extend as you need. I have personally done this and more to the data grid and several other controls for certain specific pattern handling.
In my program's main window I have a TreeView and a ContentPresenter. The display of the ContentPresenter is determined by what node is selected in the TreeView.
The name of one of my nodes is allowed to be changed by the user via contentMenu. All the user has to do is right click the node and select the new name out of the choices. The ContentPresenter is supposed to have a null display until the user chooses a name for the node.
The problem occurs when a new name is selected from the contentMenu. The ContentPresenter's display changes, like it should, but only after the user selects a different node (changing the display), and then re-selects the original node.
How do I make it so that the display on the ContentPresenter changes right when the TreeView node's name is changed?
TreeViewViewModel:
public class TreeViewViewModel : PropertyChangedBase
{
public TreeViewViewModel()
{
Node = new Node() { NodeName = "Blank", NodeDataModel = new NodeModel(),
Commands = { new Command(nodeType_name1), new Command(nodeType_name2) } };
}
//These functions call to the NodeName property in the TreeView's Data Model
private void nodeType_name1()
{
Node.NodeName = "Name1";
}
private void nodeType_name2()
{
Node.NodeName = "Name2";
}
}
XAML for MainWindow:
<!-- Tree view items & Functions -->
<TreeView Name="Tree_One" ItemsSource="{Binding DataTree.Data}" ... >
<TreeView.Resources>
<SolidColorBrush Color="LightSkyBlue" x:Key="{x:Static SystemColors.HighlightBrushKey}" />
</TreeView.Resources>
</TreeView>
<!--- Left Widget -->
<ContentPresenter Content="{Binding LeftWidget}" />
MainWindowViewModel:
public class MainWindowViewModel : PropertyChangedBase
{
private TreeViewViewModel _dataTree;
public MainWindowViewModel()
{
_dataTree = new TreeViewViewModel();
}
public TreeViewViewModel DataTree { ... }
//This function is in charge of changing the display of the ContentPresenter
// I think that my problem can probably be solved by doing something here
public void ChangeViews()
{
if (_dataTree.SelectedItem is Node)
{
var _node = _dataTree.SelectedItem as Node;
var nodeViewModel = new NodeViewModel(_node.NodeDataModel);
if (_node.NodeName== "Unknown")
LeftWidget = null; //This is the Content Presenter **
if (_node.NodeName == "Name1")
{
LeftWidget = nodeViewModel;
}
if (_node.NodeName == "Name2") {...}
}
}
}
Duh, thats a alot of code and its pretty difficult to understand what you up to since you seem to have controls in your ViewModel.
Or at least it looks to me that you have them in ViewModel. That is not very MVVM-alike my friend. :)
"The problem occurs when a new name is selected from the contentMenu. The ContentPresenter's display changes, like it should, but only after the user selects a different node (changing the display), and then re-selects the original node."
The property changed is not being fired because the new selected value is equal to the old one.
Pretty obvious, right?... no property was actually changed
But why do you want the ContentPresenter to update itself with the value that it already has?
You said when you select a node the ContentPresenter displays it properly and when you re-select the same the ContentPresenter is not doing anything.
Its not doing anything because it think it doesnt need to. Which is true.
So the question is why would you make ContentPresenter force to refresh on each value no matter if old value is the same as new one?
Though if you want to hack/trick a little bit, you can always set ContentPresenter's Content to null before you assign another value. :)
However, post us more code and we will be able to provide you a better solution to your issue.
I was able to fix this issue by calling ChangeViews(); in my MainWindowViewModel from my TreeViewViewModel. I did this by using a delegate property in the TVVM, and adding it to my MWVM. By doing this, the display is updated whenever ChangeViews(); is called.
This is the answer that I used.
I am dynamically creating ListViewItem descendants:
class Application : ListViewItem {
. . .
class LegacyApplication : Application {
I store these first in a List of obects:
private List<object> legacyApps = new List<object>();
..this way:
if (ldbc.appIsLegacy(sPathOfExe)) {
legacyApp = new LegacyApplication(sApp, sTitle, sDesc, sIconFileName, sPathOfExe, appCategoriesForCurrentApp);
}
legacyApps.Add(legacyApp);
...and then I add them to the ListView on the main form this way:
foreach (LegacyApplication LegApp in legacyApps) {
this.listApplications.Items.Add(LegApp);
}
...but the ListView does not display them. It displays the ListView Groups I've created (and each ListViewItem is assigned to one of those groups), but not the ListViewItems themselves...
Updated with requested info:
The constructor for the ListViewItem descendant looks like so:
public LegacyApplication(String AAppName, String ATitle, String ADesc, String AIconFileName, String APathOfExe, List<String> ACategories) {
base.Name = String.Format("legapplvi{0}", AAppName);
base.Text = ATitle; // "Title" is a short description - between exe name and Description
base.ToolTipText = ADesc;
base.EnsureVisible();
// "base" above means ListViewItem; "base" below refers to our Application class*
base.Categories = ACategories;
base.ExePath = APathOfExe;
base.IconFileName = AIconFileName;
}
which adds the following properties to ListViewItem:
public string ExePath
public string IconFileName
public string Category
public List Categories
LegacyApplication adds no further properties to (our) Application class.
I'm not sure what the respondent below means by "subitems" - the ListViewItems are subitems of the ListView Groups, perhaps...?
Updated with unrequested info (TMI?):
OK, I'm thinking I can add columns this way, once all the Groups are assigned to the ListView:
for(var item in listApplications.Groups) {
listApplications.Columns.Add(item)
}
...but now, how do I add specific ListViewItems to particular columns?
Updated after getting it to (sort of) work:
Commenting out this:
listApplications.View = View.Details;
...gets the Items to show. HOWEVER, the Text is truncated, which caused me to pose another question here:
I need to display the entire Text of my ListViewItems (not truncated)
Did you try to create a ListViewItem first and then add into the List instead of using
this.listApplications.Items.Add(LegApp);
Can you provide more details with a code snippet
I'm trying to bind a collection to a DataGridView. As it turns out it's impossible for the user to edit anything in this DataGridView although EditMode is set to EditOnKeystrokeOrF2.
Here is the simplified code:
public Supplies()
{
InitializeComponent();
List<string> l = new <string>();
l.Add("hello");
this.SuppliesDataGridView.DataSource = l;
}
It also doesn't work when I change the collection type to SortableBindingList, Dictionary or even use a BindingSource.
What can be wrong here?
For me the following method works as expected:
Open your form (usercontrol, etc.) with the designer
Add a BindingSource to your form
Select the BindingSource in your form and open the properties page
Select the DataSource property and click on the down arrow
Click on Add project data source
Select Object
Select the object type you wish to handle
This should be the type that will be handled by your collection, not the CustomCollection itself!
Show the available data sources by selecting from the MenuBar Data - Show Data Sources
Drag and Drop your ItemType from the DatasSources on your form
Go into the code of your form and bind your CustomCollection to the BindingSource
var cc = new CustomCollection();
bindingSource1.DataSource = cc;
Remarks:
The DataGridView is just the last part in your chain to (dis)allow changing, adding and removing objects from your list (or CustomCollection). There is also a property AllowNew within the BindingSource and the ICollection interface has a property IsReadOnly which must be set to false to allow editing. Last but not least, the properties of your class within the collection must have a public setter method to allow changing of a value.
Try this:
public class CustomCollection { public string Value { get; set; } }
public Supplies()
{
InitializeComponent();
List<CustomCollection> l = new List<CustomCollection> { new CustomCollection { Value = "hello" } };
this.SuppliesDataGridView.DataSource = l;
}
Once you've set the DataSource property you'll then want to fire off the DataBind() method.
this.SuppliesDataGridView.DataSource = l;
this.SuppliesDataGridView.DataBind();
UPDATE:
As you rightly pointed out in the comments, the DataBind() method doesn't exist for this control.
This link might provide some helpful information: http://msdn.microsoft.com/en-us/library/fbk67b6z%28v=VS.90%29.aspx