Load and create instance of data template - c#

I have an ItemsControl used to display View of items like this:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding View}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Below is short mcve to give you an idea:
public class Item
{
public string Text { get; set; }
public object View { get; set; }
... // more properties used in bindings
}
public partial class MainWindow : Window
{
public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>();
public MainWindow()
{
InitializeComponent();
// 1
{
var control = new TextBlock();
var item = new Item { Text = "1", View = control };
BindingOperations.SetBinding(control, TextBlock.TextProperty, new Binding(nameof(Item.Text)) { Source = item });
Items.Add(item);
}
// 2
{
var control = new CheckBox();
var item = new Item { Text = "2", View = control };
BindingOperations.SetBinding(control, CheckBox.ContentProperty, new Binding(nameof(Item.Text)) { Source = item });
Items.Add(item);
}
// ... and so on
DataContext = this;
}
}
As you can see each item has pre-created View (unfortunately this can't/shouldn't be changed), which can be anything, includes binding, etc.
My question: how to move creating of View into xaml (as data templates)?
Pseudoxaml:
<SomeContainer.Resources>
<DataTemplate x:Key="type1">
<TextBlock Text="{Binding Text}" />
</DataTemplate>
<DataTemplate x:Key="type2">
<CheckBox Content="{Binding Text}" />
</DataTemplate>
</SomeContainer.Resources>
<ItemsControl ... /> <!-- same definition as early? -->
Pseudo-code
Items.Add(new Item { Text = "1", View = LoadTemplate("type1") });
Items.Add(new Item { Text = "2", View = LoadTemplate("type2") });
object LoadTemplate(string key)
{
var resource = FindResource(key);
... // what next?
}

if you absolutely have to use UIElements in view model (instead of templates), and at the same time want to declare them in xaml, then
don't use DataTemplate
use x:Shared="False" on UIElement
<Window.Resources>
<TextBlock x:Key="type1" x:Shared="False" Text="{Binding Text}"/>
<CheckBox x:Key="type2" x:Shared="False" Content="{Binding Text}"/>
</Window.Resources>
each time you request a resource, you will get a new copy
LoadTemplate method is reduced to FindResource
object LoadTemplate(string key)
{
return FindResource(key);
}

Instead of creating a UI control such as a TextBlock or a CheckBox in the view model you should create a CLR object:
public class MyTextClass
{
public string Text { get; set; }
}
...
var view = new MyTextClass();
var item = new Item { Text = "1", View = control };
You could then use a DataTemplate in the view to associate an instance of your CLR object with a control:
<DataTemplate DataType="local:MyTextClass">
<TextBlock Text="{Binding Text}" />
</DataTemplate>
When you set the DataType property of a DataTemplate without specifying an x:Key, the DataTemplate gets applied automatically to data objects of that type: https://msdn.microsoft.com/en-us/library/system.windows.datatemplate.datatype(v=vs.110).aspx

Related

Binding User control elements to Model properties

I'm trying to bind a UserControl to some model with no luck.
I have this ItemsControl:
<ItemsControl x:Name="spPatientFiles" Grid.Row="2"
ItemsSource="{Binding PatientFiles, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type uc:PatientFile}">
<uc:PatientFile/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.DataContext>
<ViewModels:MainViewModel/>
</ItemsControl.DataContext>
</ItemsControl>
It is bound by
public class MainViewModel
{
public ObservableCollection<PatientFile> PatientFiles { get; set; }
public MainViewModel()
{
PatientFiles = new ObservableCollection<PatientFile>();
if (!string.IsNullOrEmpty(Models.LocalSettings.SaveFolder) &&
System.IO.Directory.Exists(Models.LocalSettings.SaveFolder))
{
var files = System.IO.Directory.GetFiles(Models.LocalSettings.SaveFolder);
foreach (var p in files)
{
var n = p.Substring(p.LastIndexOf("\\") + 1, p.LastIndexOf(".") - p.LastIndexOf("\\") - 1);
var ext = p.Substring(p.LastIndexOf(".") + 1);
PatientFiles.Add(new PatientFile()
{
FileName = n,
Path = p,
FileType = ext == "avi" ? OutputType.Video : OutputType.Image
});
}
}
}
}
Main xaml has View Model:
<Window ...
xmlns:ViewModels="clr-namespace:Octopus.Capturing.ViewModels"x:Class="Octopus.Capturing.Views.MainApp"
...>
This is my UserControl (uc:PatientFile)
<UserControl ...
xmlns:Models="clr-namespace:Octopus.Capturing.Models"
x:Class="Octopus.Capturing.Views.Controls.PatientFile"
... >
<Label FontSize="10"
VerticalContentAlignment="Center" Foreground="White"
Content="{Binding FileName}"/>
But the binding doesn't work.
When I run the app I get the structure of the UserControl (style and correct number of files read from folder) but without the binding to the file name property.
What i'm doing wrong here?
You should not have a collection of PatientFile UserControls in a view model. Instead, create a separate view model for this kind of user control, PatientFileViewModel. Implement INotifyPropertyChanged to enable notifying the controls to update the changed properties.
public class PatientFileViewModel : INotifyPropertyChanged
{
private string _fileName;
public string FileName
{
get => _fileName;
set
{
if (_fileName == value)
return;
_fileName = value;
OnPropertyChanged();
}
}
// ...implement the other properties like this, too.
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Adapt your ItemTemplate to display the PatientFile user control for the PatientFileViewModel.
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type uc:PatientFileViewModel}">
<uc:PatientFile/>
</DataTemplate>
</ItemsControl.ItemTemplate>
Next, remove the properties and code that is now in the view model from your PatientFile user control. You can keep its XAML code. The content of the Label will now be bound to the data context, which is the PatientFileViewModel that is set by the ItemsControl automatically when applying the DataTemplate.
Finally adapt your MainViewModel to create and use PatientFileViewModels instead of PatientFiles.
public class MainViewModel
{
public ObservableCollection<PatientFileViewModel> PatientFiles { get; set; }
public MainViewModel()
{
PatientFiles = new ObservableCollection<PatientFileViewModel>();
if (!string.IsNullOrEmpty(Models.LocalSettings.SaveFolder) &&
System.IO.Directory.Exists(Models.LocalSettings.SaveFolder))
{
var files = System.IO.Directory.GetFiles(Models.LocalSettings.SaveFolder);
foreach (var p in files)
{
var n = p.Substring(p.LastIndexOf("\\") + 1, p.LastIndexOf(".") - p.LastIndexOf("\\") - 1);
var ext = p.Substring(p.LastIndexOf(".") + 1);
PatientFiles.Add(new PatientFileViewModel()
{
FileName = n,
Path = p,
FileType = ext == "avi" ? OutputType.Video : OutputType.Image
});
}
}
}
}
Make sure that you set the DataContext of your main view to the MainViewModel, e.g.:
<Window xmlns:ViewModels="clr-namespace:Octopus.Capturing.ViewModels"x:Class="Octopus.Capturing.Views.MainApp"
...>
<Window.DataContext>
<ViewModels:MainViewModel/>
</Window.DataContext>
<!-- ...other code. -->
</Window>
Furthermore, you should remove Mode=TwoWay and UpdateSourceTrigger=PropertyChanged on the ItemsSource binding, it is not needed.

Bind control to specific item in list of observable collections

I have a scenario where I have multiple lists of data that is constructed programmatically in a loop. I want to display these lists side by side on the UI.
I setup a List of ObservableCollections of strings to contain the data. I am using the ListBox way of binding to the lists as shown here: Bind textbox list inside listbox in wpf
with this XMAL:
<ListBox Name="ListTwo" ItemsSource="{Binding Source=obs}" ... >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="TextBoxList" Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
The problem is how do you bind a specific listbox to an specific index in the List? Specifically I want the 0th list to bind to List[0], 1st to bind to List[1], etc.
So took the below suggestion and tried to make things into a class. This is what I got, but the UI isn't showing the updates.
XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
x:Class="NS.ClassMainWindow"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<ListView Grid.Row="1" Grid.Column="0" x:Name="RawBox" ItemsSource="{Binding Foo.Raw}" Background="LightGray" BorderThickness="2" BorderBrush="Black">
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Name="RawItemsTextBoxList" Text="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
DataClass:
public class FooClass
{
public List<ObservableCollection<string>> items;
public ObservableCollection<string> Raw { get => this.items[0]; set => this.items[0] = value; }
public ObservableCollection<string> Tier1 { get => this.items[1]; set => this.items[1] = value; }
public ObservableCollection<string> Tier2 { get => this.items[2]; set => this.items[2] = value; }
public ObservableCollection<string> Tier3 { get => this.items[3]; set => this.items[3] = value; }
public ObservableCollection<string> Tier4 { get => this.items[4]; set => this.items[4] = value; }
public FooClass()
{
this.items = new List<ObservableCollection<string>>();
for (int i = 0; i <= 4; i++)
{
this.items.Add(new ObservableCollection<string>());
}
}
}
And Assignment:
this.Foo.Raw = new ObservableCollection<string>(itemNames); // itemNames is a List<string>
I am very obviously missing something, but for the life of me can't see it. Fairly new to WPF so probably is a noob thing.
If you set the DataContext to an instance of a FooClass and then set the Raw property a non-empty collection like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var foo = new FooClass();
foo.Raw = new ObservableCollection<string>(new List<string> { "1", "2", "3" });
DataContext = foo;
}
}
...you should see the strings provided that you bind to the Raw property and set the Mode of the TextBox to either OneTime or OneWay:
<ListView Grid.Row="1" Grid.Column="0" x:Name="RawBox" ItemsSource="{Binding Raw}"
Background="LightGray" BorderThickness="2" BorderBrush="Black">
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Name="RawItemsTextBoxList" Text="{Binding Mode=OneTime}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Databinding a nested List property in WPF

I am using the following XAML code to display a list of checked list boxes.
<ListBox x:Name="lbxProjects" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<ListBox x:Name="lbxUnits" ItemsSource="{Binding Units}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox Content="{Binding unit.Name}" IsChecked="{Binding isSelected}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The data model is as follows
public class ProjectsListBox
{
public Project project { get; set; }
public List<UnitsCheckBox> Units = new List<UnitsCheckBox>();
public ProjectsListBox(Project project)
{
this.project = project;
foreach(var d in project.Documents)
{
Units.Add(new UnitsCheckBox(d));
}
}
}
public class UnitsCheckBox : INotifyPropertyChanged
{
public Document unit { get; set; }
private bool isselected = true;
public bool isSelected
{
get { return isselected; }
set
{
isselected = value;
NotifyPropertyChanged("isSelected");
}
}
public UnitsCheckBox(Document d)
{
unit = d;
}
}
I am assigning the data source for the parent listbox like
lbxProjects.DataContext = projectsList;
The code creates the child list boxes but not the checkboxes inside the child list boxes. What am i missing?
How should WPF resolve unit.Name?
If the type UnitsCheckBox contains a Name property, then the CheckBox's Content should be bound to Name:
Content="{Binding Name}"
You should always specify the type of your DataTemplate:
<DataTemplate DataType="{x:Type local:UnitsCheckBox}" ...>
Those are the probable problems but I can't be sure unless you give us the UnitsCheckBox code.

How to do Data Binding for Textblock within a LongListselector in windows phone app?

Hi, I am trying to bind the data for text block within a LongListSelector. But I am not getting any Output for it, kindly help me.
This is my XAML code:
<phone:LongListSelector ItemsSource="{Binding ''}" x:Name="longListSelector" HorizontalAlignment="Left" Height="680" VerticalAlignment="Top" Width="446" >
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Name="name" Text="{Binding DataContext.TextContent,ElementName=page,Mode=OneWay}" Height="100" Width="100" HorizontalAlignment="Center">
</TextBlock>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
In the C# code I have parsed data which i need to display in the windows phone, in a menu format.
Part of C# code is shown below:
XDocument document = XDocument.Parse(e.Result);
var data1 = from query in document.Descendants("location")
select new Data
{
Lat = (string)query.Element("lat"),
Lag = (string)query.Element("lng")
};
foreach (var d in data1)
{
JsonParsing(d.Lat, d.Lag);
}
data1 = from query in document.Descendants("result")
select new Data
{
Country = (string)query.Element("formatted_address")
};
foreach (var d in data1)
{
// ob.JsonParsing(d.Lat, d.Lag);
//XmlParsing(d.Lat, d.Lag);
val = d.Country;
//listbox.Items.Add(val);
//StringsList.Add(val);
TextContent=val;
I want the value of the country to be shown inside the textblock, kindly help me figure this out as I am pretty new to this field, thanks.
try like this
a good reference
<DataTemplate>
<StackPanel VerticalAlignment="Top">
<TextBlock Text="{Binding Value}" />
</StackPanel>
</LongListSelector>
CodeBehind
**Add a public property only public property can be participate in databinding**
#region Public Properties
private ObservableCollection<YourModel> _collectionofValue;
public ObservableCollection<YourModel> CollectionofValues
{
get
{
return _collectionofValue;
}
set
{
_collectionofValue=value;
raisepropertyChanged("CollectionofValues");
}
}
private string _value;
public string Value
{
get
{
return _errorMessage;
}
set
{
_errorMessage = value;
RaisePropertyChanged("Value");
}
}
#endregion
**Set value to this public property when you get value**
// for single values
public void getValue()
{
value =GetXmlValue(); // your method that will return the value;
}
// as it is a collection
public void getValuestoCollection()
{
Collection.Add(new YourModel(value="SampleValue1");
Collection.Add(new YourModel(value="SampleValue1");
Collection.Add(new YourModel(value="SampleValue1");
Collection.Add(new YourModel(value="SampleValue1");
}
YourModel
// the collection of this model is binded to the LongListSelector.
public class ModelName
{
public string Values {get;set;}
}
reference
<phone:LongListSelector ItemsSource="{Binding Items}" x:Name="longListSelector" HorizontalAlignment="Left" Height="680" VerticalAlignment="Top" Width="446" >
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Name="name" Text="{Binding Path=TextContent}" Height="100" Width="100" HorizontalAlignment="Center">
</TextBlock>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
Your C# algm should be:
i) Have a viewmodel class
public class MyViewModel
{
public ObservableCollection<MyDataItem> Items {get; set;}
public MyViewModel()
{
Items=new ObservableCollection<MyDataItem>();
loop //add your items to your 'Items' property so that you can bind this with LongListSelector ItemsSource
{
Items.Add(new MyDataItem("mystring"));
}
}
}
public class MyDataItem
{
public MyDataItem(string s)
{
TextContent=s;
}
public string TextContent {get;set;}
}
ii) Create an instance to ViewModel class and set DataContext
// write this in the constructor of the page which contains the LongListSelector
public MyViewModel vm;
constructor()
{
vm=new MyViewModel();
this.DataContext=vm;
}

How to bind an ObservableCollection to a Listpicker

I am attmepting to bind an ObservableCollection of items to a ListPicker from the Silverlight toolkit for Windows Phone. I have done this before, but in my case now, my ObservableCollection contains items from a custom class. I do not know how to get each of the properties of the class (for each item) to bind to my ListPicker. To better illustrate what I have is as follows:
MainPage.xaml
<Grid.Resources>
<DataTemplate x:Name="SearchProviderItemTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Favicon}" />
<TextBlock Text="{Binding Name}" Margin="12,0,0,0"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Name="SearchProviderFullModeItemTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Favicon}" />
<TextBlock Text="{Binding Name}" Margin="12,0,0,0"/>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<toolkit:ListPicker x:Name="SearchProviderListPicker" ItemsSource="{Binding SearchProvider}" Margin="12,0,12,0"
Header="Search provider" ItemTemplate="{Binding SearchProviderItemTemplate}"
FullModeHeader="Search provider" FullModeItemTemplate="{Binding SearchProviderFullModeItemTemplate}"
SelectedIndex="{Binding}"
SelectionChanged="SearchProviderListPicker_SelectionChanged"
CacheMode="BitmapCache"/>
MainPage.xaml.cs
public MainPage()
{
InitializeComponent();
//This data is placed here for convenience right now
ListItem Bing = new ListItem { Favicon = "", Name = "Bing", Address = "http://www.bing.com/search?q=" };
ListItem Google = new ListItem { Favicon = "", Name = "Google", Address = "http://www.google.com/search?q=" };
ListItem Yahoo = new ListItem { Favicon = "", Name = "Yahoo", Address = "http://search.yahoo.com/search?p=" };
ListItem Ask = new ListItem { Favicon = "", Name = "Ask", Address = "http://www.ask.com/web?q=" };
ListItem Aol = new ListItem { Favicon = "", Name = "AOL", Address = "http://search.aol.com/search?q=" };
Settings.SearchProvider.Value.Add(Bing);
Settings.SearchProvider.Value.Add(Google);
Settings.SearchProvider.Value.Add(Yahoo);
Settings.SearchProvider.Value.Add(Ask);
Settings.SearchProvider.Value.Add(Aol);
// Set the data context of the SearchProviderListPicker control to the data
DataContext = App.ViewModel;
}
MainViewModel.cs
public ObservableCollection<ListItem> SearchProvider { get; private set; }
public MainViewModel()
{
SearchProvider = Settings.SearchProvider.Value;
}
The Settings class referenced above is used to store the SearchProvider ObservableCollection in isolated storage.
Settings.cs
public static Setting<ObservableCollection<ListItem>> SearchProvider = new Setting<ObservableCollection<ListItem>>("SearchProvider", new ObservableCollection<ListItem>());
And the Setting class saves and gets the data from isolated storage.
Also, my custom ListItem class demonstrates the properties I need to use.
ListItem.cs
public string Favicon
{
get;
set;
}
public string Name
{
get;
set;
}
public string Address
{
get;
set;
}
So basically each SearchProvider ObservableCollection item contains the Favicon, Name, and Address that I need to use, and I only want to bind the Favicon and Name to the ListPicker. My problem though, is the ListPicker presents the 5 search providers except the text for each says Project1.Common.ListItem where the ListItem class in in my Common folder. I must not be binding these correctly to the view, but I do not know how to properly accomplish this?
Your ListPicker's templates are Static resources defined in Grid.Resources
Hence you have to change the ListPicker xaml code like the this
ItemTemplate="{Binding SearchProviderItemTemplate}"
FullModeItemTemplate="{Binding SearchProviderFullModeItemTemplate}"
replace the above two properties to the following
ItemTemplate="{StaticResource SearchProviderItemTemplate}"
FullModeItemTemplate="{StaticResource SearchProviderFullModeItemTemplate}"
If you are getting 'Project1.Common.ListItem' as the text that means your binding is correct but there is a problem with your template.
The first correction is as given above, you need to use "StaticResource" instead of "Binding" when you are referring to templates.
Please recheck your template for any typos etc.
If i were you, i would first try to make it work without using the Settings class, See if that helps

Categories