I don't know according to MVVM show data on control.
I have a collection of cars.
I want group their by type (eg. Sedan, Combi, Hatchback) and depends of number of types print grids.
So :
5 cars:
2 x sedan, 2 x Combi, 1 x sportcar.
So I want to print 3 grids.
How do it to be ok with MVVM.
Below is some sample code. If your lists of cars can change you should use ObservableCollections instead or implement INotifyPropertyChanged on your viewmodel.
XAML:
<Window x:Class="TestApp.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid>
<ListBox ItemsSource="{Binding Path=CarTypes}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Key}" />
<ListBox ItemsSource="{Binding Path=Value}" DisplayMemberPath="Name" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
</Window>
Code behind:
using System.Collections.Generic;
using System.Windows;
namespace TestApp
{
public partial class Window2 : Window
{
public Window2()
{
InitializeComponent();
DataContext = new CarsVM();
}
}
public class CarsVM
{
public CarsVM()
{
CarTypes = new Dictionary<string, List<Car>>();
// You want to populate CarTypes from some model.
CarTypes["sedan"] = new List<Car>() {new Car("Honda Accord"), new Car("Toyota Camry")};
CarTypes["musclecar"] = new List<Car>() { new Car("Chevy Camaro"), new Car("Dodge Challenger") };
CarTypes["suv"] = new List<Car>() { new Car("Chevy Tahoe") };
}
public Dictionary<string, List<Car>> CarTypes { get; private set; }
}
public class Car
{
public Car(string name)
{
Name = name;
}
public string Name { get; set; }
}
}
Related
I have been pounding my head on a table today trying to get my drag and drop events to fire. The drag and drop functionality works on the interface, but the events wont fire. I need the events to fire so I can update the database with the new order of the objects. What am I doing wrong?
In the code below, I place break points in the 'Drop' and 'DragOver' methods, but they never get hit.
XAML:
<Window x:Class="Reorder_item_WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:dd="clr-namespace:GongSolutions.Wpf.DragDrop;assembly=GongSolutions.Wpf.DragDrop">
<Grid>
<ListBox Grid.Column="1" SelectionMode="Extended" ItemsSource="{Binding MSPCollection}"
dd:DragDrop.IsDragSource="True" Width="300" Margin="0,0,5,0" dd:DragDrop.IsDropTarget="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="#2ba3d5" Height="50" Width="280">
<TextBlock Text="{Binding Name}" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="40"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
c#:
public class MSP {
public int Id { get; set; }
public string Name { get; set; }
}
class MainViewModel : IDropTarget
{
public ObservableCollection<MSP> MSPCollection { get; set; }
public MainViewModel() {
MSPCollection = new ObservableCollection<MSP>();
MSPCollection.Add(new MSP() {
Id = 1,
Name = "Anis Derbel"
});
MSPCollection.Add(new MSP()
{
Id = 2,
Name = "Firas Mdimagh"
});
MSPCollection.Add(new MSP()
{
Id = 3,
Name = "Khaled Jemni"
});
MSPCollection.Add(new MSP()
{
Id = 4,
Name = "Sahbouch"
});
}
public void DragOver(IDropInfo dropInfo) {
if (dropInfo.Data is MSP) {
dropInfo.DropTargetAdorner = DropTargetAdorners.Highlight;
dropInfo.Effects = DragDropEffects.Move;
}
}
public void Drop(IDropInfo dropInfo) {
MSP msp = (MSP)dropInfo.Data;
((IList)dropInfo.DragInfo.SourceCollection).Remove(msp);
}
}
You also need to set the DropHandler via the respective Attached Property:
<ListBox ItemsSource="{Binding Collection}"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.DropHandler="{Binding}" />
From documentation
Okay, sorry for my previous mess.
The situation is this:
I have two custom objects defined as follows:
MainObject:
public class MainObject
{
private string mainObjectName;
public string MainObjectName { get { return mainObjectName; } }
private List<SubObject> subObjectData;
public List<SubObject> SubObjectData { get { return subObjectData; } }
public MainObject(string name, List<SubObject> objectData)
{
mainObjectName = name;
subObjectData = objectData;
}
}
SubObject:
public class SubObject
{
private string subObjectName;
public string SubObjectName { get { return subObjectName; } }
private List<int> integerData;
public List<int> IntegerData { get { return integerData; } }
public SubObject(string name, List<int> data)
{
subObjectName = name;
integerData = data;
}
}
I also have a viewmodel which for simplicity defines some data using those two objects as follows:VM
public List<Model.MainObject> VMList = new List<Model.MainObject>()
{
new Model.MainObject("MainItem1", new List<Model.SubObject>()
{
new Model.SubObject("SubItem1", new List<int>() { 1,6,3}),
new Model.SubObject("SubItem2", new List<int>() { 5,2,9})
}),
new Model.MainObject("MainItem2", new List<Model.SubObject>()
{
new Model.SubObject("SubItem1", new List<int>() { 0,3,1}),
new Model.SubObject("SubItem2", new List<int>() { 7,5,2})
})
};
now I have the following UI
<Grid>
<ItemsControl Name="MainObjectIC">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding MainObjectName}"/>
<ItemsControl Name="SubObjectIC">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SubObjectName}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
I assign the ItemsSource of the MainObjectIC in the code behind like so:
ViewModel.VM dc = new ViewModel.VM();
public MainWindow()
{
InitializeComponent();
DataContext = dc;
MainObjectIC.ItemsSource = dc.VMList;
}
I also want to assign the ItemsSource to the SubObjectIC but to do that I have to get that ItemsControl object. And this is what I am trying to achieve.
From what I understood it may be a very very bad and useless to assign the ItemsSource property from the code behind.
Thank you for improving your code example. It still wasn't quite complete, but it's close enough to be able to provide an answer.
In your example, the main thing missing is to simply add the necessary {Binding} expression. In particular:
<ItemsControl Name="SubObjectIC" Grid.Column="1"
ItemsSource="{Binding SubObjectData}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SubObjectName}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The context for the item already is an object of type MainObject (which is why your TextBlock binding works). So all that remains to be done is bind the ItemsSource property to the MainObject.SubObjectData property.
(I had to add the Grid.Column assignment, which appeared to be missing from your example above.)
The above change is completely sufficient to get your example to work as you desire. However, you can also improve the code by using the same basic approach to the top-level control as well. To do so, your VM.VMList field needs to be changed to be a property (WPF only binds to properties, not fields):
class VM
{
public List<MainObject> VMList { get { return _vmList; } }
private readonly List<MainObject> _vmList = new List<MainObject>()
{
new MainObject("MainItem1", new List<SubObject>()
{
new SubObject("SubItem1", new List<int>() { 1,6,3}),
new SubObject("SubItem2", new List<int>() { 5,2,9})
}),
new MainObject("MainItem2", new List<SubObject>()
{
new SubObject("SubItem1", new List<int>() { 0,3,1}),
new SubObject("SubItem2", new List<int>() { 7,5,2})
})
};
}
Then you can just remove the explicit assignment that's in your constructor:
public MainWindow()
{
InitializeComponent();
DataContext = dc;
}
With those changes, your XAML no longer needs to give any of the controls names, and you can bind directly to the relevant properties:
<Window x:Class="TestSO42929995WpfNestedData.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestSO42929995WpfNestedData"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl ItemsSource="{Binding VMList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding MainObjectName}"/>
<ItemsControl Grid.Column="1"
ItemsSource="{Binding SubObjectData}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SubObjectName}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
A key point that might not be obvious in the above is that every control has a DataContext. When you using the {Binding} syntax, by default the property path is relative to that context. In the top-level control, the context is what you set it to be in the constructor. But in the individual list item template, the context is the individual data object for that list item, which in your case is the MainObject object. So in that context, you just bind to the SubObjectData property, just like you bind to the MainObjectName. It works exactly the same, and for the same reason.
I'm trying to bind a property that was defined in Page code-behind to a ListView.DataContext property in XAML, but for some reason it's not working in the way that I thought, when I run the app the ListView.DataContext is not being set and remains null, can someone please help me with that?
I looked for some similar questions but most of them solve the problem by setting the DataContext manually in the code-behind, but I'd like to do that from XAML.
MainPage.xaml
<Page
x:Class="CustomControls.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CustomControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<ListView
DataContext="{Binding Path=MyMarket, RelativeSource={RelativeSource Mode=Self}}"
ItemsSource="{Binding Path=Products}"
Header="{Binding Path=Name}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Id}"/>
<TextBlock Text="{Binding Path=Description}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Page>
MainPage.xaml.cs
using System.Collections.ObjectModel;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace CustomControls
{
public sealed partial class MainPage : Page
{
public Market MyMarket { get; private set; }
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
this.MyMarket = new Market
{
Name = "My Market",
Products = new ObservableCollection<Product>
{
new Product { Id = 123, Description = "qwerty" },
new Product { Id = 234, Description = "wertyu" }
}
};
}
}
public class Product
{
public int Id { get; set; }
public string Description { get; set; }
}
public class Market
{
public string Name { get; set; }
public ObservableCollection<Product> Products { get; set; }
}
}
You have to bind to the page, so you have to do that at the very top of your XAML:
<Page
[..]
DataContext="{Binding MyMarket, RelativeSource={RelativeSource Self}}">
Then you should be able to hook into that like this:
<ListView
ItemsSource="{Binding Path=Products}"
Header="{Binding Path=Name}">
[..]
Now just switch the lines in your constructor so that your elements are already there before the page is built:
this.MyMarket = new Market
{
Name = "My Market",
Products = new ObservableCollection<Product>
{
new Product { Id = 123, Description = "qwerty" },
new Product { Id = 234, Description = "wertyu" }
}
};
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
You should consider using Viewmodel classes later on.
{RelativeSource Mode=Self} refers to the current element, in this case ListView, so it's not what you want. The easiest way is to give a name to the root element (the Page), for instance "root", and specify ElementName=root in your binding.
I have a StackPanel in my grid and it displays a dynamically generated list of buttons, I'm trying to figure out how to get them displayed ascending alphabeticaly.
XAML Code
<StackPanel Grid.Column="0" Name="AreaStackPanel" Orientation="Vertical" Background="Khaki">
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" Background="Beige">
<GroupBox Name="StatusGroupBox" Header="Work Items" Width="234">
<StackPanel Name="StatusStackPanel"></StackPanel>
</GroupBox>
</StackPanel>
C# Code
private void LoadExistingAreas()
{
List<string> collections = Reporter.GetCollections();
string unique = "";
foreach (string collection in collections)
{
string areaName = Path.GetFileName(collection);
if (unique.Contains(areaName)) continue;
unique += areaName;
Button areaButton = new Button();
areaButton.Click += areaButton_Click;
areaButton.Margin = new Thickness(2);
areaButton.Content = areaName;
AreaStackPanel.Children.Add(areaButton);
Area
}
}
I would recommend using MVVM to accomplish this task. I am posting an example of what would work in a fairly clean fashion.
Your XAML should look as follows:
<Window x:Class="ItemTemplateDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ItemTemplateDemo"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding ButtonDescriptions}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="2" Content="{Binding Name}" Command="{Binding OnClickCommand}"></Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
The main view model. You should sort and filter your data in here as well
public class MainViewModel
{
public ObservableCollection<ButtonDescription> ButtonDescriptions { get; private set; }
public MainViewModel()
{
ButtonDescriptions = new ObservableCollection<ButtonDescription>();
for (int i = 0; i < 10; i++)
{
var bd = new ButtonDescription() { Name = "Button " + i };
ButtonDescriptions.Add(bd);
}
}
}
The button description holds the attributes for the button
public class ButtonDescription
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private ICommand onClickCommand;
public ICommand OnClickCommand
{
get { return onClickCommand; }
set { onClickCommand = value; }
}
public ButtonDescription()
{
}
}
I would also recommend reading the following if you are not familiar with MVVM MVVM intro
Is there a way to access the containers generated for an itemscontrol's items?
For example,
Given an ItemsControl with an ItemTemplate as shown below, access the actual TextBlock within the DataTemplate generated for each item in the control. (not the object, but its associated textblock).
Views/MainPage.xaml:
<phone:PhoneApplicationPage
x:Class="PhoneApp1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:PhoneApp1.ViewModels"
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls. Toolkit"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<!--LayoutRoot is the root grid where all page content is placed-->
<phone:Pivot>
<phone:Pivot.Resources>
<vm:PersonViewModel x:Key="ViewModel"/>
</phone:Pivot.Resources>
<phone:PivotItem>
<phone:PivotItem.Header>
<TextBlock Text="Pivot"/>
</phone:PivotItem.Header>
<ItemsControl x:Name="People" DataContext="{StaticResource ViewModel}" ItemsSource="{Binding People}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock toolkit:SlideInEffect.LineIndex="{Binding PersonLineIndex}" Text="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</phone:PivotItem>
<phone:PivotItem>
<phone:PivotItem.Header>
<TextBlock Text="Empty Pivot"/>
</phone:PivotItem.Header>
</phone:PivotItem>
</phone:Pivot>
</phone:PhoneApplicationPage>
Views/MainPage.xaml.cs
using Microsoft.Phone.Controls;
namespace PhoneApp1
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
}
}
}
ViewModels/PersonViewModel.cs
using PhoneApp1.Models;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace PhoneApp1.ViewModels
{
public class PersonViewModel
{
public PersonViewModel()
{
People = new ObservableCollection<Person>
{
new Person { Name = "Joe" },
new Person { Name = "Jack" },
new Person { Name = "James" },
new Person { Name = "John" }
};
PersonLineIndex = new List<int>();
for (int i = 0; i < People.Count; i++)
{
PersonLineIndex.Add(i);
}
}
public ObservableCollection<Person> People { get; set; }
public List<int> PersonLineIndex { get; set; }
}
}
Models/Person.cs:
namespace PhoneApp1.Models
{
public class Person
{
public string Name { get; set; }
}
}
Why would I need this access? For example:
Try to set a different line index for each textblock. Without adding a "LineIndex" property to your Person (as that would violate MVVM).
I figured it out. You would access such ItemsControl's ItemContainerGenerator
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
People.Loaded += People_Loaded;
}
void People_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
for (int i = 0; i < People.Items.Count; i++)
{
var container = People.ItemContainerGenerator.ContainerFromIndex(i);
container.SetValue(SlideInEffect.LineIndexProperty, i);
}
}
}