I implemented a DataGrid via WPF MVVM where I'd like to "scroll" to a particular item. I don't want to select the correspond row though. Using the CurrenItem property works fine, but it "scrolls" to the target row at the bottom of the data grid (or more precisely to the full row at the bottom - there can be also be a partially displayed row below the target row).
Below an over-simplified version of what I implemented :
public class ViewModel
{
public ObservableCollection<ItemModel> Items;
public ItemModel CurrentItem { get; set; }
public ViewModel()
{
Items = new ObservableCollection<ItemModel>();
..
CurrentItem = Items[..];
}
}
<DataGrid
CurrentItem="{Binding CurrentItem}"
ItemsSource="{Binding Items}">
<!-- .. -->
</DataGrid>
PS I don't want to use external framework.
Thanks for any insights.
Related
I've got a ComboBox (inside a ListView) that used to be tied to a collection of strings. However, I'm switching to having it use a custom class instead.
Now the ComboBox is bound to an ObservableCollection of type Area. For display, I'm showing the Name property with DisplayMemberPath. This works great, but the box is no longer loading with the current value selected.
<ListView x:Name="TestListView" ItemsSource="{Binding TestViewList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.View>
<GridView>
<GridViewColumn Header="Area">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.AreaList, ElementName=BVTWindow}" DisplayMemberPath="Name" SelectedItem="{Binding Path=Area, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
public ObservableCollection<ViewTest> TestViewList { get; private set; }
public ObservableCollection<Area> AreaList { get; private set; }
public class ViewTest : BindableBase
{
public string Description { get; set; }
public Area Area { get; set; }
}
public partial class Area
{
public long ID { get; set; }
public string Name { get; set; }
}
"Area" is the same class as the Collection, and is a property of the class the ListView's ItemsSource is bound to. Am I doing this the right way? Any help is much appreciated.
UPDATE:
I had a theory that perhaps using the "Name" property (which is a string) for display in the box made the SelectedItem attribute look for a string rather than something of type Area. I changed the class for TestViewList to use a string to keep track of Area, but that didn't change the program's behavior at all.
UPDATE 2:
Above, I've added the pertinent lines from the view model related to the ComboBox and ListView in question.
Update 3:
I've changed my Area property from inline to expanded, including the SetProperty that handles raising. It now works for new items added to the ItemSource at run-time, but is still a blank selection for on items loaded at program start.
public class ViewTest : BindableBase
{
public string Description { get; set; }
public Area Area
{
get
{
return this.area;
}
set
{
this.SetProperty(ref this.area, value);
}
}
}
Update 4:
It turns out Paul Gibson's answer was correct. The reason my existing entries weren't loading properly had to do with a logic error in the way I was loading items from the database, and not a problem with the xaml bindings. Everything works now (with respect to those ComboBoxes, at least).
Based on what you are saying I think my comment is the answer. This is the case for a single combobox. In your case you need one for every line of the grid, so you may have to programatically add the binding to members of a list by index.
In your view model if you also have (single case):
private Area _curSelArea;
public Area curSelArea
{
get { return _curSelArea; }
set
{
_curSelArea = value;
RaisePropertyChanged("curSelArea");
}
}
Then you can bind to the property with:
SelectedItem="{Binding DataContext.curSelArea . . . }"
The view model can set the initial value of curSelArea if it is known initially.
EDIT: After actually having to do this I found that someone extended the DataGridComboBoxColumn to facilitate better binding. Check out this link if you are trying to do this: http://joemorrison.org/blog/2009/02/17/excedrin-headache-35401281-using-combo-boxes-with-the-wpf-datagrid/
Whenever I used TreeView I always had just few nodes and each of them usually had less than 100 items. I never really needed any kind of ui virtualization for that but now for the first time I need it.
The problem appears when using ui virtualization with recycling mode the TreeView seems to expand items even though I never expanded them manually.
I googled the issue and as far I understood recycling mode of virtualization in TreeView the containers get reused.
So I assume that the cause might be applying already expanded reused container to an item which wasn't expanded before.
Here is a simple example:
https://github.com/devhedgehog/wpf/
For those who cannot download code for whatever reason here is basically what I have tried to do with the TreeView.
This is what I have in XAML.
<Grid>
<TreeView ItemsSource="{Binding}" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Parts}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
And this is code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
IList<Car> list = new List<Car>();
for (int i = 0; i < 5000; i ++)
{
list.Add(new Car() { Name = "test1" + i });
}
foreach (var car in list)
{
car.Parts = new List<string>();
for (int i = 0; i < 500; i++)
{
car.Parts.Add("asdf" + i);
}
}
this.DataContext = list;
}
}
public class Car
{
public string Name
{
get;
set;
}
public List<string> Parts
{
get;
set;
}
}
I hope somebody can provide me a solution to this issue. Is this a known bug?
I am sorry in case its a duplicate. Futhermore I hope you guys tell me what I did wrong since this is my first post before you downgrade the question.
As you probably know, this problem can be solved easily by using standard recycling mode:
<TreeView VirtualizingStackPanel.VirtualizationMode="Standard" ...>
This shouldn't have too much of an impact on your TreeView's performance, as the tree will still be virtualized and a container will only be created for visible items. The benefits of the recycling mode only come into play when scrolling (when items are both being virtualized and realized), and usually the standard virtualization mode is good enough.
However, in case performance is really critical (or if you really want a solution for this while keeping the recycling mode, or if you're looking to do things the right way), you can use backing data and data binding to solve this problem.
The reason why this problem occurs in the first place is this:
Let's say you have a TreeViewItem which has its IsExpanded property set to true. When it's being recycled, i.e. its data is replaced, its IsExpanded property remains the same because it has no way to know whether it should be expanded or not, because that data is not available anywhere. The only place where it exists is the IsExpanded property of the TreeViewItem, and it's not going to be relevant because that item is being reused along with its properties.
If however you have a viewmodel for each tree item you'll be able to bind each TreeViewItem to the IsExpanded property in your TreeViewItemViewModel (you will have a view model for each tree item) and you will always get the correct value because you've made that data available and bound each item to it.
Your TreeView's ItemsSource will be bound to a collection of TreeViewItemViewModel objects, and your TreeViewItemViewModel class will look something like this:
class TreeViewItemViewModel : INotifyPropertyChanged
{
bool IsExpanded { get; set; }
bool IsSelected { get; set; }
TreeViewItemViewModel Parent { get; }
ObservableCollection<TreeViewItemViewModel> Children { get; }
}
You can find more information on how exactly to create such view model in Josh Smith's excellent article Simplifying the WPF TreeView by Using the ViewModel Pattern.
I have a simple datastructure following:
In the model I have
public class Receipt
{
public int Id { get; set; }
public double Price { get; set; }
public string Store { get; set; }
public DateTime Date { get; set; }
}
I've made two of these objects and I am trying to bind them to a datagrid. I've filled in the properties of the two receipts and added them to the dataGridRows but they don't show up in my DataGrid.
public MainWindow()
{
InitializeComponent();
makeReceipts()
}
public ObservableCollection<Receipt> dataGridRows = new ObservableCollection<Receipt>();
public Receipt receipt1 = new Receipt();
public Receipt receipt2 = new Receipt();
public void makeReceipts()
{
receipt1.Id = 1;
receipt1.Price = 10;
receipt1.Store = "Brugsen";
receipt1.Date = DateTime.Today;
receipt2.Id = 2;
receipt2.Price = 15;
receipt2.Store = "Netto";
receipt2.Date = DateTime.Today;
dataGridRows.Add(receipt1);
dataGridRows.Add(receipt2);
}
And in the xaml of the MainWindow where I want my datagrid to display the receipts I have:
<DataGrid Name="ReceiptGrid" CanUserResizeColumns="True" IsReadOnly="True" AutoGenerateColumns="True" ItemsSource="{Binding Source=dataGridRows}" />
What am I doing wrong?
first you can just bind to public properties.
so if you want to use binding you have at least do:
public ObservableCollection<Receipt> dataGridRows {get;set;}
second you have to do two steps:
set the right datacontext
set the right binding expression(Path)
assume that the datacontext for yyour grid is an object with the property dataGridRows, your binding should look like this
<DataGrid ItemsSource="{Binding Path=dataGridRows}" .../>
Think your problem is you have to write
ItemsSource="{Binding Path=dataGridRows}"
and not
ItemsSource="{Binding Source=dataGridRows}"
source is to specify another control in xaml file
First of all you can bind only public properites so you need to change definition of dataGridRows to something like this:
public ObservableCollection<Receipt> dataGridRows { get; set; }
then you don't bind it as a Source but as a Path, however since your dataGridRows is defined in MainWindow you need to specify Source as your MainWindow otherwise it will look in default DataContext which is not set in your case
<DataGrid ... ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=dataGridRows}" />
this tells Binding to find Window and look for a dataGridRows property there.EDIT:Normally you don't put data into view. I suggest you read more about MVVM design pattern but basically the idea is that you have your ViewModel where you put in you whole application logic, unaware of interface, and then on top you have your view to interact with user but in ViewModel you don't operate on controls.What you should do is create your view-model class with dataGridRows property and assign it through DataContext of Window for example. Each FrameworkElement has it and when you don't specify Binging source (Source, RelativeSource, ElementName) it will try to resolve Binding.Path in current DataContext. If current control does not have it specified then if will go to parent in visual tree and so on.
What would be the best way to get the elements of a combobox to each support a Command and CommandParameter?
I'd like to implement the Theme Chooser shown toward the bottom of this blog post, except with a combo box instead of a context menu. I'd need each element of the combobox to support a Command and CommandParameter, and I'd like it to just be plain text, as the combo below is.
<ComboBox>
<ComboBox.Items>
<TextBlock>A</TextBlock>
<TextBlock>B</TextBlock>
<TextBlock>C</TextBlock>
</ComboBox.Items>
</ComboBox>
I tried hyperlinks, but the main problem there is that when you click directly onto the link text, the combo box does not close.
Is there an easy way to do this?
EDIT
Ok, well the specific goal that I said I wanted to achieve—having a combo change the SL Toolkit theme—is trivially accomplished. I can simply bind the selected item of the combo to a ViewModel property that then exposes the appropriate themeuri which my SL Toolkit theme can bind to, or, since this is purely a UI activity with no business logic, I can just catch the combobox item changed event, and update my themeUri from there.
I am curious though, is there a good way to bind each combo box item to a command with a command parameter? Using a Hyperlink as each comboboxItem seemed promising, but that prevents the CB from closing after you click on an item when you click the actual hyperlink.
You could Bind the selected item to your ViewModel and then the setter would trigger when the Theme was changed.
Xaml:
<ComboBox SelectedItem="{Binding SelectedTheme, Mode=TwoWay}" ItemsSource="{Binding Themes}" />
CodeBehind:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
DataContext = new MainPageViewModel();
}
}
ViewModel:
public class MainPageViewModel : INotifyPropertyChanged
{
public ObservableCollection<string> Themes { get; set; }
private string _selectedTheme;
public string SelectedTheme
{
get { return _selectedTheme; }
set
{
_selectedTheme = value;
// Change the Theme
RaisePropertyChanged("SelectedTheme");
}
}
public MainPageViewModel()
{
Themes = new ObservableCollection<string>();
Themes.Add("Red");
Themes.Add("Green");
Themes.Add("Blue");
}
}
I am trying set up databinding as described in the title.
The problem I having is binding to a generic list.
Any examples out there.
I can't use BindingListCollectionView on a generic list so have to use
CollectionView.
The issue I am puzzled about is to add a new item
on click of Add button I add a new item to the
generic list and refresh the View. But if user
does not follow through the list now has empty
item.
I know this is basic but how is that handled normally?
Malcolm
I see two questions here and I'll try to answer them step by step.
Binding list of items with detail view
Given these ViewModel classes (imagine everyone implements INotifyPRopertyChanged):
public class DataView {
public Item SelectedItem {get; set; }
public List<Item> Items { get; private set; }
}
public class Item {
public string Title { get; set; }
}
Putting a Data instance into the DataContext, a minimal View might look like this:
<StackPanel>
<ListView Items="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
<TextBox Text="{Binding SelectedItem.SelectedItem.Title}" />
</StackPanel>
Adding new items
To be able to create a new Item without immediately adding it to the list, you might want to split off the newly created object into its own area. Visually you can either have it in a new pop up or integrated into the list, but actually it'll be only added to the list on the next try to add or acknowledge the parent dialog. At this point you can also verify whether the Item is valid enough to add it to the list.