I have a view (xaml) which contains a stacklayout with the id "CheckBoxList". From the viewmodel I would like to add some checkboxes to this stacklayout. But I don't know how. I can't use
CheckBoxList.Children.Add(checkbox);
Is there a easy way to do that?
thanks
I have a view (xaml) which contains a stacklayout with the id "CheckBoxList". From the viewmodel I would like to add some checkboxes to this stacklayout. But I don't know how.
Not recommended but this could be a way: You can bind the whole StackLayout(CheckBoxList) to a ContentView.Content:
Define the CheckBoxList in your ViewModel:
public class MainPageViewModel
{
public StackLayout CheckBoxList { get; set; }
public MainPageViewModel()
{
//You can modify the codes as you want
CheckBoxList = new StackLayout();
for (int i = 0; i < 30; i++)
{
CheckBoxList.Children.Add(new MyCheckBox { IsChecked = i % 2 == 1 ? true : false });
}
}
}
In your xaml use a ContentView and bind CheckBoxList to it:
<StackLayout x:Name="root">
<ContentView Content="{Binding CheckBoxList}"></ContentView>
</StackLayout>
Binding your viewmodel in code behind:
public partial class MainPage : ContentPage
{
MainPageViewModel ViewModel { get; set; }
public MainPage()
{
InitializeComponent();
ViewModel = new MainPageViewModel();
this.BindingContext = ViewModel;
}
}
Here is the complete demo
Related
MAUI.NET
I have a special picker for my own that I want to reuse in many places
<ContentView ...
x:Class="XYZ.Views.ABCPicker"
x:DataType="views:ABCPicker">
<Grid>
<Picker ItemsSource="{Binding ...}" SelectedItem="{Binding ChoosenDeviceTypeProperty}"/>
</Grid>
</ContentView>
and its code behind with my AttachedProperty:
public partial class ABCPicker : ContentView
{
public static readonly BindableProperty ChoosenDeviceTypeProperty = BindableProperty.CreateAttached("ChoosenDeviceType", typeof(string), typeof(ABCPicker), "Detect by connect", BindingMode.TwoWay);
public static string GetChoosenDeviceType(BindableObject view)
{
return (string)view.GetValue(ChoosenDeviceTypeProperty);
}
public static void SetChoosenDeviceType(BindableObject view, string value)
{
view.SetValue(ChoosenDeviceTypeProperty, value);
}
public ABCPicker()
{
this.BindingContext = this;
InitializeComponent();
}
}
I want to consume it in my broader control ViewModel
public class BroaderControlViewModel : BaseViewModel
{
...
private string myResult;
public string MyResult
{
get { return myResult; }
set { SetPropertyAndNotify(ref myResult, value); }
}
public ICommand MyCommand { private set; get; }
public BroaderControlViewModel()
{
MyCommand = new Command(() =>
{
// here I want the to get a choosen value to proceed with it after the user have choosen value in my special picker
});
}
}
with a view like below:
<ContentView ...
x:Class="XYZ.Views.BroaderControlView"
x:DataType="viewModels:BroaderControlViewModel">
<VerticalStackLayout ...>
<views:ABCPicker ChoosenDeviceType="{Binding MyResult, Mode=TwoWay}" />
<Button Text="Connect" Command="{Binding MyCommand}"/>
</VerticalStackLayout>
</ContentView>
I tried different binding types, also with x:Reference this and also with normal binding properties - not attached ones.
How it should be matched?
I have a very simple form that I'm using to experiment with BindableProperty. Here's the XAML for the form
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyBindableProperty"
x:Class="MyBindableProperty.MainPage">
<StackLayout>
<local:MyLabel x:Name="BindingLabel" Text="{Binding Text}" MyText="{Binding Text}"
VerticalOptions="Center"
HorizontalOptions="Center" />
<Entry x:Name="BindingEntry" Text="{Binding Text, Mode=TwoWay}"/>
<Entry x:Name="BindingEntry2" Text="{Binding Text, Mode=TwoWay}"/>
<Button x:Name="BindingButton" Text="Reset"/>
</StackLayout>
</ContentPage>
And here is the code behind
public partial class MainPage : ContentPage
{
public DataSourceClass DataSourceObject { get; set; }
public MainPage()
{
DataSourceObject = new DataSourceClass { Text = "Test1" };
BindingContext = DataSourceObject;
InitializeComponent();
BindingButton.Clicked += BindingButton_Clicked;
}
private void BindingButton_Clicked(object sender, EventArgs e)
{
var boundText = this.BindingLabel.Text;
var boundMyText = this.BindingLabel.MyText;
DataSourceObject.Text = "Test2";
}
}
Finally, here is the custom label class used in the XAML -
public class MyLabel : Label
{
public string MyText
{
get
{
return (string)GetValue(MyTextProperty);
}
set
{
SetValue(MyTextProperty, value);
}
}
public static readonly BindableProperty MyTextProperty = BindableProperty.Create(nameof(MyText), typeof(string), typeof(MyLabel), "Test", BindingMode.TwoWay, propertyChanged: MyTextChanged);
public static void MyTextChanged(BindableObject bindable, object oldValue, object newValue)
{
((MyLabel)bindable).TextChanged(newValue.ToString());
}
public void TextChanged(string newText)
{
Device.BeginInvokeOnMainThread(() => this.Text = newText);
}
}
The issues I'm having are
when the page initialises the MyTextChanged handler fires, but not after any subsequent changes
when the Reset button is clicked the value in DataSourceObject.Text is correctly updated with the value from the Entry element
no matter how I try to set the values of BindingLabel and BindingEntry2 they never reflect the values of DataSourceObject.Text after the page has loaded.
Any help would be greatly appreciated.
I stumbled across this so I updated the DataSourceClass from this
public class DataSourceClass
{
public string Text { get; set; }
}
to this
public class DataSourceClass : INotifyPropertyChanged
{
private string _text;
public string Text
{
get
{
return _text;
}
set
{
_text = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Text"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
and now everything works.
I thought BindableProperty was meant to supersede INotifyPropertyChanged?
what is the easiest way to bind selected state of listview's item to model's boolean property?
I have model:
class Model {
public string Name { get; set; }
public bool Selected { get; set; }
}
And listview:
<ListView x:Name="myListView" SelectionMode="Multiple">
<ListView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
I bind items to the listview:
myListView.ItemsSource = // list of Model instances
I want the Selected property of Model to always reflect whether it is selected or not in myListView. So - by selecting/deselecting the item in myListView, it will hold the apropriate bool value or by setting Selected property myListView will select/deselect appropriate item.
what is the easiest way to bind selected state of listview's item to model's boolean property?
I'm not sure if this is the most easiest way, but for me I think it is the easiest way to bind SelectorItem.IsSelected property of ListViewItem to your Selected property in model. Only the problem is, we all know each item of ListView is an instance of ListViewItem, but when we use DataTemplate to build the item structure for ListViewItem, ListViewItems are not available in design-time. So my idea is to bind this property in code behind, just for example here:
<ListView x:Name="myListView" SelectionMode="Multiple" Loaded="myListView_Loaded" ItemsSource="{x:Bind Collection}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
code behind:
private ObservableCollection<Model> Collection = new ObservableCollection<Model>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
for (int i = 0; i < 100; i++)
{
Collection.Add(new Model { Name = "Name " + i });
}
}
private void myListView_Loaded(object sender, RoutedEventArgs e)
{
IEnumerable<ListViewItem> lvItems = FindVisualChildren<ListViewItem>(myListView);
if (lvItems != null)
{
foreach (ListViewItem lvitem in lvItems)
{
Model model = lvitem.Content as Model;
Binding b = new Binding
{
Source = model,
Path = new PropertyPath("Selected"),
Mode = BindingMode.TwoWay,
};
BindingOperations.SetBinding(lvitem, ListViewItem.IsSelectedProperty, b);
}
}
}
private static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
public class Model : INotifyPropertyChanged
{
public string Name { get; set; }
private bool _Selected;
public bool Selected
{
get { return _Selected; }
set
{
if (value != _Selected)
{
_Selected = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I think the perfect way would be to implement the ListView.ItemSelectionChanged event, and iterate through each item in the list view model to set it to true for the item which is selected, false for the rest items.
However, You may want to try something like this, although honestly I'm not sure if this is the correct way:
class Model {
public string Name { get; set; }
public bool Selected
{
get
{
return MyListView.SelectedItems.Count(x => x.Name == Name) > 0;
}
}
}
I'm trying to create a TemplateSelector which recognizes if an implements an interface and applies a DataTemplate for it.
I'd like to use this selector in following way:
<ListView Grid.Column="0"
ItemsSource="{Binding Media}"
SelectionMode="None">
<ListView.ItemTemplateSelector>
<selectors:InterfaceAwareTemplateSelector>
<DataTemplate x:Key="IMedia">
<Image Source="{Binding PreviewImage}" />
</DataTemplate>
<DataTemplate x:Key="IDocument">
<TextBlock Text="test" />
</DataTemplate>
</selectors:InterfaceAwareTemplateSelector>
</ListView.ItemTemplateSelector>
</ListView>
I end up with following implementation:
[ContentProperty(Name = "Items")]
public class InterfaceAwareTemplateSelector: DataTemplateSelector {
public DataTemplate DefaultTemplate { get; set; }
public Dictionary<Type, DataTemplate> Items { get; set; }
public InterfaceAwareTemplateSelector() {
Items = new Dictionary<Type, DataTemplate>();
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var result = (
from t in Items
where t.Key.GetTypeInfo().IsAssignableFrom(item.GetType().GetTypeInfo())
select t.Value).FirstOrDefault();
return result ?? DefaultTemplate;
}
}
It of course doesn't work, otherwise I wouldn't write this question :) Application crushes with a message a xaml cannot be parsed:
A first chance exception of type 'Windows.UI.Xaml.Markup.XamlParseException' occurred in Hicron.ProductCatalog.MainUI.exe
WinRT information: E_UNKNOWN_ERROR [Line: 47 Position: 39]
An exception of type 'Windows.UI.Xaml.Markup.XamlParseException' occurred in Hicron.ProductCatalog.MainUI.exe but was not handled in user code
WinRT information: E_UNKNOWN_ERROR [Line: 47 Position: 39]
Additional information: Unspecified error
What's wrong with that dictionary? Normally I'd use CompositeCollection and merge multiple sources but this class is missing in WinRT :(
EDIT
In terms of fixing dictionary problem I've changed dictionary to list of custom types. Still can't create a custom type with Type set from XAML. I could use a string but than I can't manage it in code unless I specify fully qualified type name.
[ContentProperty(Name = "Items")]
public class InterfaceAwareTemplateSelector: DataTemplateSelector {
public DataTemplate DefaultTemplate { get; set; }
public List<InterfaceAwareTemplateSelectorItem> Items { get; set; }
public InterfaceAwareTemplateSelector() {
Items = new List<InterfaceAwareTemplateSelectorItem>();
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) {
if (item == null) {
return DefaultTemplate;
}
var result = (
from t in Items
where t.Type.GetTypeInfo().IsAssignableFrom(item.GetType().GetTypeInfo())
select t.Template).FirstOrDefault();
return result ?? DefaultTemplate;
}
}
public class InterfaceAwareTemplateSelectorItem
{
public Type Type { get; set; }
public DataTemplate Template { get; set; }
}
Corresponding XAML:
// somewhere in page tag
xmlns:bo="using:/*long long namespace*/.BusinessObjects"
// somewhere in XAML file
<ListView Grid.Column="0"
ItemsSource="{Binding Media}"
SelectionMode="None">
<ListView.ItemTemplateSelector>
<selectors:InterfaceAwareTemplateSelector>
<selectors:InterfaceAwareTemplateSelectorItem Type="bo:IMedia">
<selectors:InterfaceAwareTemplateSelectorItem.Template>
<DataTemplate>
<Image Source="{Binding PreviewImage}"
Tapped="ImageTapped" />
</DataTemplate>
</selectors:InterfaceAwareTemplateSelectorItem.Template>
</selectors:InterfaceAwareTemplateSelectorItem>
<selectors:InterfaceAwareTemplateSelectorItem Type="bo:IDocument">
<selectors:InterfaceAwareTemplateSelectorItem.Template>
<DataTemplate>
<TextBlock Text="pa8u4mrapwu" />
</DataTemplate>
</selectors:InterfaceAwareTemplateSelectorItem.Template>
</selectors:InterfaceAwareTemplateSelectorItem>
</selectors:InterfaceAwareTemplateSelector>
</ListView.ItemTemplateSelector>
</ListView>
Okay, so using this:
public interface IFake1 { }
public interface IFake2 { }
public class TemplateItem
{
public DataTemplate Template { get; set; }
public string Interface { get; set; }
}
public class MySelector : DataTemplateSelector
{
public List<TemplateItem> Templates { get; set; }
}
I could do this:
<GridView>
<GridView.ItemTemplateSelector>
<local:MySelector>
<local:MySelector.Templates>
<local:TemplateItem Interface="IFake1">
<local:TemplateItem.Template>
<DataTemplate>
<!-- TODO -->
</DataTemplate>
</local:TemplateItem.Template>
</local:TemplateItem>
<local:TemplateItem Interface="IFake2">
<local:TemplateItem.Template>
<DataTemplate>
<!-- TODO -->
</DataTemplate>
</local:TemplateItem.Template>
</local:TemplateItem>
</local:MySelector.Templates>
</local:MySelector>
</GridView.ItemTemplateSelector>
</GridView>
The error appears in the Type you are using. I could not get that to work. Had to use String. Should be simple to parse form there.
Best of luck!
See if it works if you replace Dictionary<Type, DataTemplate> with ResourceDictionary. I'm betting at least one of the problems is that the key in x:Key="IMedia" can't be implicitly converted to Type. You could also just try using string as the key type.
I finally managed to fix it. Unfortunately I couldn't get a conversion from string to Type in XAML so I had to stick to strings :/ Not very convenient but at least works. That's what I ended up with:
XAML
<ListView Grid.Column="0"
ItemsSource="{Binding Media}"
SelectionMode="None">
<ListView.ItemTemplateSelector>
<selectors:InterfaceAwareTemplateSelector>
<DataTemplate x:Key="IMedia">
<Image Source="{Binding PreviewImage}" Tapped="ImageTapped"/>
</DataTemplate>
<DataTemplate x:Key="IDocument">
<commonItems:DocumentItemPresenter TappedCommand="{Binding DataContext.OpenDocument, ElementName=PageRoot}"/>
</DataTemplate>
</selectors:InterfaceAwareTemplateSelector>
</ListView.ItemTemplateSelector>
</ListView>
Selector itself:
[ContentProperty(Name = "Items")]
public class InterfaceAwareTemplateSelector: DataTemplateSelector {
public DataTemplate DefaultTemplate { get; set; }
public Dictionary<string, DataTemplate> Items { get; set; }
public InterfaceAwareTemplateSelector() {
Items = new Dictionary<string, DataTemplate>();
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) {
if (item == null) {
return DefaultTemplate;
}
var result = (
from ii in item.GetType().GetTypeInfo().ImplementedInterfaces
from dt in Items
where ii.Name == dt.Key
select dt.Value).FirstOrDefault();
return result ?? DefaultTemplate;
}
}
I upvoted Filip & Jerry because I found your tips helpful. Thank you guys.
If anyone is interested how this problem was solved below is final implementation and a usage example.
Usage:
<GridView ....>
<GridView.ItemTemplateSelector>
<selectors:InterfaceAwareTemplateSelector>
<!-- ReSharper disable once Xaml.RedundantResource -->
<DataTemplate x:Key="INewsContainer" selectors:InterfaceAwareTemplateSelector.Priority="1">
<ctrls:ItemsContainerTile Width="350" Height="350"
ItemTappedCommand="{Binding DataContext.OpenNewsDetails, ElementName=PageRoot}"/>
</DataTemplate>
<!-- ReSharper disable once Xaml.RedundantResource -->
<DataTemplate x:Key="ISimpleMaterial" selectors:InterfaceAwareTemplateSelector.Priority="0">
<ctrls:GenericTile Width="350" Height="350"
TappedCommand="{Binding DataContext.OpenDetails, ElementName=PageRoot}" />
</DataTemplate>
</selectors:InterfaceAwareTemplateSelector>
</GridView.ItemTemplateSelector>
.... the rest of XAML
The priority is to control the order in which datatamples should be checked. This way we can control what happens if multiple keys matches the object being converted.
Implementation:
[ContentProperty(Name = "Items")]
public class InterfaceAwareTemplateSelector: DataTemplateSelector {
public DataTemplate DefaultTemplate { get; set; }
public Dictionary<string, DataTemplate> Items { get; set; }
public InterfaceAwareTemplateSelector() {
Items = new Dictionary<string, DataTemplate>();
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) {
if (item == null) {
return DefaultTemplate;
}
var results = (
from ii in item.GetType().GetTypeInfo().ImplementedInterfaces
from dt in Items
where ii.Name == dt.Key
select dt)
.ToArray();
if (results.Length > 1) {
var orderedResults =
from r in results
where IsPrioritySet(r.Value)
orderby GetPriority(r.Value) descending
select r;
if (orderedResults.Any()) {
return orderedResults.First().Value;
}
throw new AmbigiousResolveTemplateFound(item.GetType(), results.Select(x => x.Key));
}
else if (results.Length == 1) {
return results[0].Value;
}
return DefaultTemplate;
}
#region PriorityProperty
public static readonly DependencyProperty PriorityProperty =
DependencyProperty.RegisterAttached(
"Priority",
typeof(int),
typeof(InterfaceAwareTemplateSelector),
new PropertyMetadata(0));
public static int GetPriority(DependencyObject item) {
if (item == null) { throw new ArgumentNullException("item"); }
return (int)item.GetValue(PriorityProperty);
}
public static void SetPriority(DependencyObject item, int value) {
if (item == null) { throw new ArgumentNullException("item"); }
item.SetValue(PriorityProperty, value);
}
public static bool IsPrioritySet(DependencyObject item) {
if (item == null) { throw new ArgumentNullException("item"); }
var result = item.ReadLocalValue(PriorityProperty);
return result != DependencyProperty.UnsetValue;
}
#endregion
}
Hope someone will find this implementation helpful. Once again, many thanks to Filip and Jerry for your help.
i am trying to show the current sysdatetime in Top App bar and i was wondering anyway i can do that in XAML for win store apps.
public class MainViewModel : INotifyPropertyChanged
{
private readonly DispatcherTimer _timer = new DispatcherTimer();
private string _resDateTime;
public string ResDateTime
{
get
{
return _resDateTime;
}
set
{
_resDateTime = value;
NotifyPropertyChanged("ResDateTime");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
public MainViewModel()
{
_timer.Tick += TimerOnTick;
_timer.Start();
}
private void TimerOnTick(object sender, object o)
{
ResDateTime = DateTime.Now.ToString();
}
}
add to the code behind
public MainPage()
{
this.InitializeComponent();
DataContext = new MainViewModel();
}
and put on xaml
<Page.TopAppBar>
<AppBar>
<TextBlock Text="{Binding ResDateTime}"></TextBlock>
</AppBar>
</Page.TopAppBar>
hope it will help
In your page you can set Page.TopAppBar, and Page.BottomAppBar like this:
<Page.TopAppBar>
<AppBar>
<TextBlock Text="Your text" />
</AppBar>
</Page.TopAppBar>
From there, you can whether bind the Text property, if you're using the MVVM pattern, or simply assign a value in the code behind of the page, by giving a name to the TextBlock element.