Listview binding issue - c#

I'm having following classes:
class MyViewModel
{
public List<MyItem> MyItems { get; set; }
public int Width { get; set; }
}
class MyItem
{
public string Name {get; set;}
}
As you see, there's a list of MyItems and Width property in the same class called MyViewModel. How can I bind a single element of that list to a Text property in XAML and Width from ViewModel to XAML's Width property? Here's my try, but I can't at the same time bind those two properties. I mean, I can bind whole list to Text property, but I don't know how could I bind a single item.
<ListView ItemsSource="{Binding MyViewModel}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Height="15" Width="520">
<TextBlock Width="{Binding Width}" Text="{Binding=MyItems.Name(?)}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>

You should revise your design, but here is a quick fix: just introduce a readonly property, that returns the first element, so you will have this (assuming MyItems always has at least element, otherwise you will get an exception):
class MyViewModel
{
public List<MyItem> MyItems { get; set; }
public int Width { get; set; }
public MyItem FirstElement { get { return MyItems[0]; } }
}
In your xaml you bind TextBlock to this property:
<ListView ItemsSource="{Binding MyViewModel}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Height="15" Width="520">
<TextBlock Width="{Binding Width}" Text="{Binding=FirstElement}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
A little bit offtopic, but still important: viewmodel classes often implement INotifyPropertyChanged, so that views will be able to update themselves automatically. For the same reason List<T> should be replaced with ObservableCollection<T>.

Related

Binding ItemsControl Items Property to outside Source Object

I have a List of objects of type MenuModel called MenuList inside my ViewModel. I am using CaliburnMicro framework
I would like to show this list as a list of ToggleButtons that have IsChecked property bound to other object list called SelectedMenusMonday, which is list of type SelectedMenuModel that has only IsSelected property and is the same length as MenuList.
MenuModel looks like this:
public class MenuModel
{
public int MenuKey { get; set; }
public string MenuName { get; set; }
public string Description { get; set; }
}
MenuList:
public List<MenuModel> MenuList
{
get { return _MenuList; }
set => Set(ref _MenuList, value);
}
SelectedMenuModel
public class SelectedMenuModel
{
public bool IsSelected { get; set; }
}
And SelectedMenusMonday list:
private BindableCollection<SelectedMenuModel> _SelectedMenusMonday = new BindableCollection<SelectedMenuModel>();
public BindableCollection<SelectedMenuModel> SelectedMenusMonday
{
get { return _SelectedMenusMonday; }
set => Set(ref _SelectedMenusMonday, value);
}
I am trying to display like this:
<ItemsControl x:Name="MondayMenuList" ItemsSource="{Binding MenuList}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Content="{Binding MenuName}" IsChecked="{Binding Path=DataContext.SelectedMenusMonday.IsSelected, RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The MenuList and SelectedMenus Monday get filled from SQL DB. This is the solution i tried, but it does not work. Can someone help me please! I want the ToggleButtons to be "checked" if the item on the SelectedMenusMonday have IsSelected property as true.
Thank you very much!
Name the root element in your view (or wherever you know the DataContext to be correct) and use ElementName binding as shown here:
<UserControl x:Name="view">
<Grid>
<ItemsControl x:Name="MondayMenuList" ItemsSource="{Binding MenuList}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Content="{Binding MenuName}" IsChecked="{Binding ElementName=view, Path=DataContext.SelectedMenusMonday}">
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
Note the x:Name="view" in the UserControl element.

XAML Collection Databinding

I am working on simple card game, could you please enlight how to instantiate collection and add something to it.
Here is my basic Card class and Cards class that contains ObservableList of Cards
class Card
{
public String Name { get; set; }
}
class Cards
{
public ObservableCollection<Card> CardCollection { get; set; }
}
Here is my XAML, note that binding is OK but the list is empty I have no idea how to add items on list.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.DataContext>
<local:Cards x:Name="Cards"></local:Cards>
</Grid.DataContext>
<TextBlock Text="Cards"
FontFamily="Segoe UI"
FontSize="42"></TextBlock>
<ListView Margin="10,60,10,10" ItemsSource="{Binding CardCollection}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Height="50" Background="White">
<TextBlock Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
You can also do like this.
class Cards
{
private ObservableCollection<Card> _CardCollection = new ObservableCollection<Card>();
public ObservableCollection<Card> CardCollection
{
get
{
return _CardCollection;
}
set
{
_CardCollection=value;
OnPropertyChanged("CardCollection"); //Implement property changed event
}
}
}
Note: You may have to implement propertychanged event if you want the ListView to reflect changes in ObservableCollection you have binded.
Edit: You can Add and Remove items from ObservableCollection from Add() and Remove() methods. Take a look at this page for all the methods available for ObservableCollection http://msdn.microsoft.com/en-us/library/ms668604(v=vs.110).aspx
You can add an item to the collection here like this
CardCollection.Add(new Card{Name="Ace of spades"});
Edit 2:
Yes you can use implement the ICommand Interface and bind a command to your controls. Take a look at this http://www.markwithall.com/programming/2013/03/01/worlds-simplest-csharp-wpf-mvvm-example.html
If you just want to get the instance binded to your XAML. You can access it like this in your Mainpage.xaml.cs
var bind = (Cards)DataContext;
You can get binded collection like this bind.CardCollection
I have found the answer.
Collection property must be instatiated before it can be used.
I have added it in constructor of my Cards class
class Cards
{
public ObservableCollection<Card> CardCollection { get; set; }
public Cards()
{
CardCollection = new ObservableCollection<Card>();
}
}

Sorting ItemsControl items based on a property defined declaratively in DataTemplate XAML

I'm looking for a simple way to sort items of ItemsControl based on a property specified in implicit DataTemplate for the items to which the control is bound. And defining the properties on DataTemplate is crucial here, because I cannot add the sorting property on the item itself.
So, for the below example VM layer:
public interface INamed
{
string Name { get; set; }
}
public class FirstModel : INamed
{
public string Name { get; set; }
}
public class SecondModel : INamed
{
public string Name { get; set; }
}
public class ViewModel
{
public ViewModel()
{
Models = new INamed[] { new SecondModel {Name = "Second"}, new FirstModel {Name = "First"}};
}
public IEnumerable<INamed> Models { get; private set; }
}
and this attached property:
public static class AttachedProperties
{
public static int GetSortOrder(DependencyObject obj)
{
return (int)obj.GetValue(SortOrderProperty);
}
public static void SetSortOrder(DependencyObject obj, int value)
{
obj.SetValue(SortOrderProperty, value);
}
public static readonly DependencyProperty SortOrderProperty =
DependencyProperty.RegisterAttached("SortOrder", typeof(int), typeof(AttachedProperties), new PropertyMetadata(0));
}
I have the following DataTemplate definitions (over-simplified):
<DataTemplate DataType="{x:Type local:FirstModel}">
<StackPanel Background="Red" local:AttachedProperties.SortOrder="1">
<Label>First's Name:</Label>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SecondModel}">
<StackPanel Background="Green" local:AttachedProperties.SortOrder="2">
<Label>Second's Name:</Label>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
Somewhere the usage will be like:
<ItemsControl ItemsSource="{Binding Models}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
And here the order of the items should be based on the attached property I defined for the data templates. Don't see any option to use the CollectionViewSource directly here, may be I'm wrong...
Current options I see, none too appealing, are:
Attached behavior on the ItemsControl, traversing the visual tree of each new item and sorting the Items in accordance with the found SortOrder value
A custom ItemsControl with it's own sorting logic, panel, blackjack and... you know
Wrapping the model instances in some kind of proxy with SortOrder property on it. Which still requires some custom/user control code-behind or ViewModel class changes
Is there some better/easier way I miss?
I guess you cant
I think the only way is to implement your own ItemsControl
Or wrap the models with another class
Maybe this helps:
SortDescription with custom attached property

How do I notify changes for a bound property that wholly depends on an ObservableCollection?

I have a bound property on a class, Foo which is defined similar to as follows (edited for clarity),
public class Foo : INotifyPropertyChanged
{
public Foo()
{
// This should notify when IsHidden changes?
MyApp.ViewModel.HiddenCategories.CollectionChanged += (s, e) => {
this.NotifyPropertyChanged("IsHidden");
};
}
public CategoryId Category { get; set; }
// IsHidden depends on a `global' ObservableCollection object on the ViewModel
public bool IsHidden
{
get { return MyApp.ViewModel.HiddenCategories.Contains(this.Category); }
}
// IsHidden is toggled by adjusting the global ObservableCollection - how to notify the UI?
public void ToggleHidden()
{
if (this.IsHidden)
MyApp.ViewModel.HiddenCategories.Remove(this.Category);
else
MyApp.ViewModel.HiddenCategories.Add(this.Category);
}
#region INotifyPropertyChanged members
...
}
The ViewModel has the following defined on it,
public class FooRegion
{
public string RegionName { get; set; }
// Foos is bound in the top ListBox DataTemplate
// Each Foo has properties bound in the sub ListBox DataTemplate
public ObservableCollection<Foo> Foos { get; set; }
}
// This is actually what is bound to the top level ListBox
public ObservableCollection<FooRegion> FoosByRegion { get; set; }
public ObservableCollection<CategoryId> HiddenCategories { get; set; }
My XAML defines two ItemTemplates in the resources as follows,
<phone:PhoneApplicationPage.Resources>
...
<DataTemplate x:Key="MainItemTemplate">
<StackPanel >
<TextBlock Text="{Binding Name}"/>
<ListBox ItemsSource="{Binding Foos}"
ItemTemplate="{StaticResource SubItemTemplate}" />
</StackPanel>
</DataTemplate>
...
<DataTemplate x:Key="SubItemTemplate">
<StackPanel Opacity="{Binding IsHidden, Converter={StaticResource BoolToOpacity}}" >
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem Header="{Binding IsHidden, ConverterParameter=unhide foo|hide foo,
Converter={StaticResource BoolToStrings}}" Tap="toggleHideFooContextMenuItem_Tap" />
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<TextBlock Text="Some Text Here"/>
</StackPanel>
</DataTemplate>
...
</phone:PhoneApplicationPage.Resources>
These resources are called on to a 'nested' ListBox as follows,
<ListBox ItemTemplate="{StaticResource MainItemTemplate}" ItemsSource="{Binding FoosByRegion}" />
This method appears to only work piecemeal, Some Foo objects are updated in the UI, but others are not - as if the notification is not reaching the UI.
How should I be tackling this problem?
ContextMenu from the Windows Phone Toolkit applies an animation which affects the opacity of the surrounding elements. Applying the opacity to the child elements individually solved the problem.

Binding list of objects to ItemsControl with custom ItemTemplate in MVVM

Current Setup
I have a custom class representing an installer file and some properties about that file, conforming to the following interface
public interface IInstallerObject
{
string FileName { get; set; }
string FileExtension { get; set; }
string Path { get; set; }
int Build { get; set; }
ProductType ProductType { get; set; }
Architecture ArchType { get; set; }
bool Configurable { get; set; }
int AverageInstallTime { get; set; }
bool IsSelected { get; set; }
}
My ViewModel has a ReadOnlyObservableCollection<IInstallerObject> property named AvailableInstallerObjects.
My View has a GroupBox containing the ItemsControl which binds to the aforementioned property.
<GroupBox Header="Products">
<ItemsControl ItemsSource="{Binding Path=AvailableInstallerObjects}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=FileName}" Margin="5" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
The binding works correctly, except it's not user friendly. 100+ items are shown.
Need Help Here
I'd like to be able to use my collection of IInstallerObjects but have the View present them with the following ItemTemplate structure.
<GroupBox Header="Products">
<ItemsControl ItemsSource="{Binding Path=AvailableInstallerObjects}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=ProductType}" Margin="5" />
<ComboBox ItemsSource="{Binding Path=Build}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
Basically I want to be able to group by the ProductType property, showing a list of the available products, with the ComboBox representing the available Build property values for IInstallerObjects of the ProductType.
I can use LINQ in the ViewModel to extract the groupings, but I have no idea how I'd bind to what I've extracted.
My research also turned up the possibility of using a CollectionViewSource but I'm not certain on how I can apply that to my current setup.
I appreciate your help in advance. I'm willing to learn so if I've overlooked something obvious please direct me to the information and I'll gladly educate myself.
If Build should be a collection type.
so your class should be structured like this as an example.
Public Class Customer
Public Property FirstName as string
Public Property LastName as string
Public Property CustomerOrders as observableCollection(OF Orders)
End Class
This should give you the expected results. Each item in the main items presenter will show first name last name and combobox bound to that customers orders.
I know it's simple but this should do.
All you have to do is declare a CollectionViewSource in your view and bind it to the ObservableCollection. Within this object you declare one or more GroupDescriptions which will split up the source into several groups.
Bind this source to the listbox, create a Template for the group description and you are done.
An example can be found here: WPF Sample Series – ListBox Grouping, Sorting, Subtotals and Collapsible Regions. More about CollectionViewSource can be found here: WPF’s CollectionViewSource
The description of your problem lead me to believe you are looking for some kind of colapsing / expanding / grouped / tree-view sort of thing.
XAML for the tree-view
<Window x:Class="WPFLab12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:WPFLab12"
Title="MainWindow" Height="350" Width="525">
<Grid>
<GroupBox Header="Products">
<TreeView ItemsSource="{Binding Path=ProductTypes}">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type loc:ProductType}"
ItemsSource="{Binding AvailableInstallerObjects}">
<TextBlock Text="{Binding Description}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:InstallerObject}">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=FileName}" Margin="5" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</GroupBox>
</Grid>
</Window>
What does that do? Well, it establishes a hierarchy of controls in the tree based on the type of data found. The first HierarchicalDataTemplate handles how to display the data for each class, and how they are related in the hierarchy. The second HierarchicalDataTemplate handles how to display each InstallerObject.
Code behind for the Main Window:
public partial class MainWindow : Window
{
public ReadOnlyObservableCollection<ProductType> ProductTypes
{
get { return (ReadOnlyObservableCollection<ProductType>)GetValue(ProductTypesProperty); }
set { SetValue(ProductTypesProperty, value); }
}
// Using a DependencyProperty as the backing store for ProductTypes. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ProductTypesProperty =
DependencyProperty.Register("ProductTypes", typeof(ReadOnlyObservableCollection<ProductType>), typeof(MainWindow), new UIPropertyMetadata(null));
public MainWindow()
{
this.InitializeComponent();
this.ProductTypes = new ReadOnlyObservableCollection<ProductType>(
new ObservableCollection<ProductType>()
{
new ProductType()
{
Description = "Type A",
AvailableInstallerObjects = new ReadOnlyObservableCollection<InstallerObject>(
new ObservableCollection<InstallerObject>()
{
new InstallerObject() { FileName = "A" },
new InstallerObject() { FileName = "B" },
new InstallerObject() { FileName = "C" },
})
},
new ProductType()
{
Description = "Type B",
AvailableInstallerObjects = new ReadOnlyObservableCollection<InstallerObject>(
new ObservableCollection<InstallerObject>()
{
new InstallerObject() { FileName = "A" },
new InstallerObject() { FileName = "D" },
})
}
});
this.DataContext = this;
}
}
This is totally cheating, though - normally the MainWindow.cs would not serve as the DataContext and have all this stuff. But for this example I just had it make a list of ProductTypes and populate each ProductType class with the InstallerObject instances.
Classes I used, note I made some assumptions and modified your class to suit this View Model better:
public class InstallerObject
{
public string FileName { get; set; }
public string FileExtension { get; set; }
public string Path { get; set; }
public int Build { get; set; }
public bool Configurable { get; set; }
public int AverageInstallTime { get; set; }
public bool IsSelected { get; set; }
}
public class ProductType
{
public string Description { get; set; }
public ReadOnlyObservableCollection<InstallerObject> AvailableInstallerObjects
{
get;
set;
}
public override string ToString()
{
return this.Description;
}
}
So, in MVVM, it seems to me that your current InstallerObject class is more of a Model layer sort of thing. You might consider transforming it in your ViewModel to a set of collection classes that are easier to manage in your View. The idea in the ViewModel is to model things similarly to how they are going to be viewed and interracted with. Transform your flat list of InstallerObjects to a new collection of hierarchical data for easier binding to the View.
More info on various ways to use and customize your TreeView: http://www.codeproject.com/Articles/124644/Basic-Understanding-of-Tree-View-in-WPF

Categories

Resources