Items Template code-behind - c#

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);
}
}
}

Related

WPF TwoWay Binding not work with CustomControl in Template

I have some problems with my Custom Control - Two way binding don't work when I use it in template.
So I have created template xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
xmlns:controls="clr-namespace:GUIControls;assembly=GUIControls"
>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
xmlns:controls="clr-namespace:GUIControls;assembly=GUIControls"
>
<ControlTemplate x:Key="YParamCombo" TargetType="ContentControl">
<controls:ParamCombo Header="MY CONTROL TEMPLATE"
Items="{Binding Items}"
PCValue="{Binding Codes[MY_CONTROL_TEMPLATE], Mode=TwoWay}"
Required="True"
MultiSelect="False"/>
</ControlTemplate>
<ControlTemplate x:Key="YComboBox" TargetType="ContentControl">
<ComboBox DisplayMemberPath="Name"
StaysOpenOnEdit="True"
ItemsSource="{Binding Items}"
SelectedValue="{Binding Codes[STANDARD_TEMPLATE], Mode=TwoWay}"
SelectedValuePath="Code"/>
</ControlTemplate>
MainWindow.xaml
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
xmlns:controls="clr-namespace:GUIControls;assembly=GUIControls"
Title="MainWindow" Height="250" Width="525">
<Grid Margin="0,0,0,-1">
<Button Margin="62,162,299,4" Content="Show Codes-1" Click="Button_Click2"></Button>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<HeaderedContentControl Header="STANDARD CONTROL XAML" >
<ComboBox DisplayMemberPath="Name"
StaysOpenOnEdit="True"
ItemsSource="{Binding Items}"
SelectedValue="{Binding Codes[STANDARD_XAML]}"
SelectedValuePath="Code"/>
</HeaderedContentControl>
<HeaderedContentControl Header="STANDARD CONTROL TEMPLATE" >
<ContentControl Height="23" Template="{StaticResource YComboBox}"/>
</HeaderedContentControl>
<ContentControl Height="44" Template="{StaticResource YParamCombo}">
</ContentControl>
<controls:ParamCombo Header="MY CONTROL XAML"
Items="{Binding Items}"
PCValue="{Binding Codes[MYCONTROL_XAML], Mode=TwoWay}"
Required="True"
MultiSelect="False"/>
</StackPanel>
</Grid>
cs
using System.Linq;
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = new WModel();
InitializeComponent();
}
private WModel vm { get { return (DataContext as WModel); } }
private void Button_Click2(object sender, RoutedEventArgs e)
{
MessageBox.Show(string.Join(";", vm.Codes._codes.Select(x => x.Key + "=" + x.Value).ToArray()));
}
}
}
using GUIControls;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace WpfApp1
{
public class WModel
{
public WModel()
{
Codes = new CodesClass();
}
public string Caption { get; set; }
public ObservableCollection<Dict> Items
{
get
{
return new ObservableCollection<Dict>()
{
new Dict(){ Name = "Name1", Code = "Code1" } ,
new Dict(){ Name = "Name2", Code = "Code2" }
};
}
}
public CodesClass Codes { get; set; }
}
public class Dict : IDict
{
public string Name { get; set; }
public string Code { get; set; }
}
public class CodesClass
{
public Dictionary<string, object> _codes;
public CodesClass()
{
_codes = new Dictionary<string, object>();
}
public object this[string param]
{
get
{
if (_codes.ContainsKey(param))
return _codes[param];
else
return null;// "I have no " + param;
}
set
{
_codes[param] = value;
}
}
}
}
When I run app and select all 4 comboboxes and Press button, I can see that twoway binding in one combobox(Custom Control declared in template ) do not work
---------------------------
---------------------------
STANDARD_XAML=Code2;STANDARD_TEMPLATE=Code2;MYCONTROL_XAML=Code2
---------------------------
ОК
---------------------------
Some code from control
public static readonly DependencyProperty PCValueProperty =
DependencyProperty.Register("PCValue", typeof(string), typeof(ParamCombo),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnPCValuePropertyChanged)));
//new PropertyMetadata(new PropertyChangedCallback(OnValuePropertyChanged)));, new PropertyChangedCallback(OnPCValuePropertyChanged))
#endregion
private static void OnPCValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
ParamCombo paramCombo = (ParamCombo)sender;
paramCombo.UpdateSelected((e.NewValue == null) ? "" : e.NewValue.ToString());
}
Thanks for your help!
I have had problems with twoway binding in a combo in a customcontrol template and the solution was to override OnApplyTemplate in the custom control, using Template.FindName to get the combo, get the DataContex object of the combo and raise PropertyChanged in the DataContext object for the bound property. My problem was to update the combo when the window was loaded.

WPF/C# Assigning a ViewModel to a custom control from parent view

I'm very new to C# and WPF, and I'm struggling a bit to get data where I need it.
I have one master set of data, which needs to be shared with various user controls, each of which have their own ViewModel. The problem is that I don't seem to be able to assign a ViewModel to a control from the parent XAML and then access that ViewModel from within the custom control's XAML.
I bind the control to a Viewmodel, but then the datacontext within the control doesn't allow me to access that model within the xaml, or I can set the datacontext in the user control so I can access its viewmodel, but then I can't bind to the viewmodel in xaml (because the binding is looking in the local datacontext, not the parent).
I may be going about this all wrong, most examples I've seen seem to instantiate a ViewModel in the custom control xaml, but then I don't see how you get that ViewModel to reference the correct DataModel (or specific part of the datamodel).
The following hopefully explains what I am trying to do.
Firstly I have my data model, in DataModel.cs
using System;
using System.Collections.Generic;
namespace BindingTest1
{
public class DataModel
{
private List<string>[] _dataLists;
public List<string>[] DataLists
{
get { return _dataLists; }
}
public DataModel()
{
List<string> list0 = new List<string> { "One", "Two", "Three" };
List<string> list1 = new List<string> { "Alpha", "Beta", "Gamma" };
_dataLists = new List<String>[] { list0, list1 };
}
}
}
In MainViewModel.cs
namespace BindingTest1
{
class MainViewModel
{
private MyViewModel _myFirstViewModel;
public MyViewModel MyFirstViewModel
{
get { return _myFirstViewModel; }
}
private MyViewModel _mySecondViewModel;
public MyViewModel MySecondModel
{
get { return _mySecondViewModel; }
}
private DataModel _dataModel;
public DataModel DataModel
{
get { return _dataModel; }
}
public MainViewModel()
{
_dataModel = new DataModel();
_myFirstViewModel = new MyViewModel(_dataModel.DataLists[0]);
_mySecondViewModel = new MyViewModel(_dataModel.DataLists[0]);
}
}
}
MainWindow.xaml
<Window x:Class="BindingTest1.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:BindingTest1"
mc:Ignorable="d"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<StackPanel HorizontalAlignment="Stretch" Height="100" VerticalAlignment="Top" Orientation="Horizontal">
<!-- These were just to check the data was being set up properly -->
<ListBox x:Name="listBox1" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" ItemsSource="{Binding DataModel.DataLists[0]}"/>
<ListBox x:Name="listBox2" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" ItemsSource="{Binding DataModel.DataLists[1]}"/>
<!-- this is what I want to be able to do -->
<local:MyView ViewModel="{Binding MyFirstViewModel}"/>
<local:MyView ViewModel="{Binding MySecondViewModel}"/>
</StackPanel>
</Grid>
</Window>
(Codebehind is default)
In MyViewModel.cs
using System;
using System.Collections.Generic;
namespace BindingTest1
{
public class MyViewModel
{
private List<string> _dataList;
public List<string> DataList
{
get { return _dataList; }
}
public MyViewModel(List<string> list)
{
_dataList = new List<String>(list);
_dataList.Add("Some Local Processing");
}
}
}
MyView.xaml
<UserControl x:Class="BindingTest1.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BindingTest1"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="100">
<Grid>
<ListBox x:Name="listBox" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" ItemsSource="{Binding ViewModel.DataList}"/>
</Grid>
</UserControl>
Codebehind
using System.Windows;
using System.Windows.Controls;
namespace BindingTest1
{
/// <summary>
/// Interaction logic for MyView.xaml
/// </summary>
public partial class MyView : UserControl
{
public MyViewModel ViewModel
{
get { return (MyViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(MyViewModel), typeof(MyView),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnViewModelChanged)));
public MyView()
{
InitializeComponent();
}
private static void OnViewModelChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
// Just making sure the right thing is being received
List<string> dataList = (e.NewValue as MyViewModel).DataList;
foreach(string line in dataList)
{
System.Console.WriteLine(line);
}
}
}
}
I don't think you need a dependency property here.
Try this.
<local:MyView DataContext="{Binding MyFirstViewModel}"/>
<local:MyView DataContext="{Binding MySecondViewModel}"/>
and bind the DataList to ItemsSource in the MyView XAML.
As you assigned MyFirstViewModel to the DataContext of MyView, bindings inside will look in MyFirstViewModel for the ItemsSource.
Here's how you ought to do this. Your view doesn't need a ViewModel property. It should bind to properties of its DataContext, which will be the viewmodel.
view:
ItemsSource="{Binding DataList}"
Window:
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyViewModel}">
<local:MyView
/>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel HorizontalAlignment="Stretch" Height="100" VerticalAlignment="Top" Orientation="Horizontal">
<!-- ... -->
<ContentControl Content="{Binding MyFirstViewModel}"/>
<ContentControl Content="{Binding MySecondViewModel}"/>
</StackPanel>

Binding a code-behind defined property to a DataContext property in XAML?

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.

Caliburn Micro - bind ListBox to property of objects in a collection

I'm having trouble with binding the ItemsSource of a listbox to a collection of objects and then displaying a property of those objects as the list items.
My XAML code:
<Window 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"
mc:Ignorable="d"
x:Class="CaliburnMicroBasic.ShellView"
d:DesignWidth="358" d:DesignHeight="351">
<Grid Width="300" Height="300" Background="LightBlue">
<ListBox ItemsSource="{Binding ListOfPeople}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding PersonName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
My ViewModel:
namespace CaliburnMicroBasic {
using Caliburn.Micro;
using System.Collections.ObjectModel;
using System.Windows;
public class ShellViewModel : Screen, IShell
{
public Person SelectedPerson{ get; private set; }
public ObservableCollection<Person> ListOfPeople{ get; private set; }
public ShellViewModel()
{
ListOfPeople = new ObservableCollection<Person>();
ListOfPeople.Add(new Person("Name 1"));
ListOfPeople.Add(new Person("Name 2"));
ListOfPeople.Add(new Person("Name 3"));
ListOfPeople.Add(new Person("Name 4"));
}
}
public class Person
{
public string PersonName { get; private set; }
public Person(string personName)
{
_personName = personName;
}
}
}
As you can see, I'm trying to have the listbox use Person.PersonName as the contents of each textblock in the listbox, but all I'm getting is four empty rows in the listbox. In other words, the listbox contains the correct number of items, but none of them are rendered correctly.
Can anyone see what I'm doing wrong?
You are never assigning anything to your PersonName property. Change your code to:
public Person(string personName)
{
this.PersonName = personName;
}
and remove your private field.

Problem with MVVM. Dynamic list of grids

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; }
}
}

Categories

Resources