XAML List<LineSeries> binding to chart Series - c#

I have MVVM silverlight app with toolkit charts.
In view model I created ObservableCollection property:
private ObservableCollection<LineSeries> _lines = new ObservableCollection<LineSeries>();
public ObservableCollection<LineSeries> Lines
{
get { return _lines; }
set
{
_lines = value;
NotifyPropertyChanged("Lines");
}
}
Then in some method I populate this collection with dynamic count lines:
List<SolidColorBrush> colors = BS2ColorSetHelper.GetSetColors();
for (int i = 0; i < remainderData.Count; i++)
{
LineSeries line = (colors.ElementAtOrDefault(i) != null)
? CreateNewLineSeriesWithColor(remainderData[i].DenominationName, remainderData[i].Coords, colors[i])
: CreateNewLineSeries(remainderData[i].DenominationName, remainderData[i].Coords);
line.Name = remainderData[i].DenominationName;
Lines.Add(line);
}
.........
Now I want to bind this ObservableCollection to toolkit chart series.
<toolkit:Chart Name="chart">
<toolkit:Chart.Series>
????
</toolkit:Chart.Series>
</toolkit:Chart>
I have tried
Series="{Binding Path=Lines}"
but it doesn't work. Visual Studio shows an error: Object of type 'System.Windows.Data.Binding' cannot be converted to type 'System.Collections.ObjectModel.Collection`1[System.Windows.Controls.DataVisuali‌​zation.Charting.ISeries]'. I think it's because Series are not dependency property.

Ok, we can't bind LineSeries to Series because Series are not Dependency property.
So we can create new UserControl with this dependency properties:
public class MultiChart : Chart
{
public IEnumerable SeriesSource
{
get
{
return (IEnumerable)GetValue(SeriesSourceProperty);
}
set
{
SetValue(SeriesSourceProperty, value);
}
}
public static readonly DependencyProperty SeriesSourceProperty = DependencyProperty.Register(
name: "SeriesSource",
propertyType: typeof(IEnumerable),
ownerType: typeof(MultiChart),
typeMetadata: new PropertyMetadata(
defaultValue: default(IEnumerable),
propertyChangedCallback: new PropertyChangedCallback(OnSeriesSourceChanged)
)
);
private static void OnSeriesSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
IEnumerable newValue = (IEnumerable)e.NewValue;
MultiChart source = (MultiChart)d;
source.Series.Clear();
foreach (LineSeries item in newValue)
{
source.Series.Add(item);
}
}
}
Then we just bind LineSeries to newly created property:
<common:MultiChart Name="chart"
Title="{Binding Path=Title}"
SeriesSource="{Binding Path=Lines}" />
The View Model will be:
public class ChartDenominationViewModel : ViewModel
{
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
NotifyPropertyChanged("Title");
}
}
private ObservableCollection<LineSeries> _lines = new ObservableCollection<LineSeries>();
public ObservableCollection<LineSeries> Lines
{
get { return _lines; }
set
{
_lines = value;
NotifyPropertyChanged("Lines");
}
}
}

I don't know fully the information about the control Chart I would suggest you check the documentation provided by the control originator to find out the name of the bindable property.
But from what you post my guess would be to try:
<toolkit:Chart Name="chart" Series={Binding Lines}/>
guessing that your Datacontext is all in place. meaning the datacontext of the page is set to your viewmodel

Related

Binding string items to a dependency property list of strings in xaml

I have a list of strings defined as a dependency property. I am assigning the values of the list's items via xaml. I can assign literal string values, but I do not know how to bind those values to data from viewmodel.
public static readonly DependencyProperty StringArgsProperty = DependencyProperty.Register(
"StringArgs", typeof(List<string>), typeof(GameTextBlock), new FrameworkPropertyMetadata(new List<string>()));
public List<string> StringArgs
{
get { return (List<string>)GetValue(StringArgsProperty); }
set { SetValue(StringArgsProperty, value); }
}
Here's how I can bind items to said list currently:
<common:GameTextBlock.StringArgs>
<sys:String>arg1</sys:String>
<sys:String>arg2</sys:String>
</common:GameTextBlock.StringArgs>
What I am trying to do is replace arg1/arg2 with values from ViewModel. If I wasn't assigning to the list's items I could do "{Binding NameOfField}".
I do not want to create the whole list in ViewModel and bind the list, because I want to be able to pick and choose the items using only xaml. Can this be achieved?
EDIT: A more clear example of what I want to achieve:
public class ViewModel
{
public string Item1 {get; set;}
public string Item2 {get; set;}
public string Item3 {get; set;}
}
And then I want to be able to use them in one xaml containing multiple GameTextBlocks like this:
<GameTextBlock x:Name = "txt1" >
<GameTextBlock.StringArgs>
<sys:String>{Binding VMItem1}</sys:String>
<sys:String>{Binding VMItem3}</sys:String>
</GameTextBlock.StringArgs>
</GameTextBlock>
<GameTextBlock x:Name = "txt2" >
<GameTextBlock.StringArgs>
<sys:String>{Binding VMItem1}</sys:String>
<sys:String>{Binding VMItem2}</sys:String>
</GameTextBlock.StringArgs>
</GameTextBlock>
<GameTextBlock x:Name = "txt3" >
<GameTextBlock.StringArgs>
<sys:String>{Binding VMItem1}</sys:String>
</GameTextBlock.StringArgs>
</GameTextBlock>
Static data on a xaml page cannot change. Move the list structures to the code behind and on the change handler of the dependency property move the data as needed to the property lists. Regardless, this is how it should be done.
If you need two way transfer, then you will need more code behind to move the data from the control lists on the page back to the storage; still, this can be used as a template.
Control Code behind
Data Lists
Here are the lists on the custom control to be split out to:
public partial class ShowDependency : UserControl, INotifyPropertyChanged
{
private List<string> _Text1;
public List<string> Text1
{
get { return _Text1; }
set { _Text1 = value; OnPropertyChanged(nameof(Text1)); }
}
private List<string> _Text2;
public List<string> Text2
{
get { return _Text2; }
set { _Text2 = value; OnPropertyChanged(nameof(Text2)); }
}
Dependency property with PropertyChanged Handler
Here is the dependency property which will hold the incoming list from the VM
#region public List<string> Storage
/// <summary>Storage is a transfer from the Main VM</summary>
public List<string> Storage
{
get => (List<string>)GetValue(StorageProperty);
set => SetValue(StorageProperty, value);
}
/// <summary>Identifies the Storage dependency property.</summary>
public static readonly DependencyProperty StorageProperty =
DependencyProperty.Register(
"Storage",
typeof(List<string>),
typeof(ShowDependency),
new PropertyMetadata(null, OnStoragePropertyChanged));
Change Handler
Once bound, it smurfs the data into the lists.
/// <summary>StorageProperty property changed handler.</summary>
/// <param name="d">ShowDependency that changed its Storage.</param>
/// <param name="e">Event arguments.</param>
private static void OnStoragePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ShowDependency source)
{
var value = (List<string>)e.NewValue;
if (value != null)
{
var l1 = new List<string>();
var l2 = new List<string>();
int count = 1;
foreach (var item in value)
if ((count++ % 2) == 0)
l1.Add(item);
else
l2.Add(item);
source.Text1 = l1;
source.Text2 = l2;
}
}
}
#endregion public List<string> Storage
Binding List of Strings on Main VM
new List<string>() { "Alpha", "Baker", "Tango", "Omega" }
Custom Control Page
<UserControl x:Class="ExamplesInWPF.Controls.ShowDependency"
...
d:DesignHeight="450" d:DesignWidth="800"
x:Name="MyUserControl">
<StackPanel Orientation="Vertical">
<ListBox Margin="6" ItemsSource="{Binding Text1, ElementName=MyUserControl}"/>
<ListBox Margin="6" ItemsSource="{Binding Text2, ElementName=MyUserControl}"/>
</StackPanel>
</UserControl>
Result
As an aside I have always used these Control dependency property snippets
Helpful Silverlight Snippets
Disregard the Silverlight part, they are still good and work up to .Net 6 Xaml/WPF.

Format string is ignored after TextBox gets bound to a property

In a dynamically built UserControl I have set the format string for a TextBox this way:
TextBox newTextBox = new TextBox();
TempViewModel viewModel = new TempViewModel();
var binding = new System.Windows.Data.Binding
{
Source = viewModel,
Path = new PropertyPath("DecimalValue"),
ConverterCulture = new System.Globalization.CultureInfo("de-DE"),
StringFormat = "{0:#,##0.00}"
};
newTextBox.SetBinding(TextBox.TextProperty, binding);
public class TempViewModel
{
public decimal DecimalValue { get; set; }
}
That works fine so far.
But after adding a DependencyProperty the format is ignored. The Dependencyproperty is defined this way:
public class UserControl_CBOGridQuotePositions : UserControl
{
private static readonly DependencyProperty Amount_QuotePos_Base_DependencyProperty =
DependencyProperty.Register("Amount_QuotePos_Base", typeof(System.Decimal), typeof(UserControl_CBOGridQuotePositions), new PropertyMetadata(0m));
public System.Decimal Amount_QuotePos_Base
{
get { return (System.Decimal)GetValue(UserControl_CBOGridQuotePositions.Amount_QuotePos_Base_DependencyProperty); }
set { SetValue(UserControl_CBOGridQuotePositions.Amount_QuotePos_Base_DependencyProperty, value); }
}
private void MakeTheBindings(DependencyProperty dependencyProperty)
{
var binding = new Binding("Amount_QuotePos_Base");
binding.Source = this; // which is the UserControl_CBOGridQuotePositions
newTextBox.SetBinding(dependencyProperty, binding);
}
}
Is there a way to make the format working while the TextBox is bound to a property?
Because in MakeTheBindings() you are replacing your Binding with a new one. Make sure when you do this var binding = new Binding("Amount_QuotePos_Base"); that you also set all the properties such as ConverterCulture and StringFormat

Xamarin forms set Picker SelectedItem

I am working with a xamarin Forms.
I am using Picker for DropDownList.
How can I set selectedItem to Picker?
My code
<Picker x:Name="VendorName" Title="Select" ItemDisplayBinding="{Binding VendorName}" SelectedItem="{Binding VendorName}" Style="{StaticResource PickerStyle}"></Picker>
and server side code is
Device.BeginInvokeOnMainThread(() =>
{
VendorName.ItemsSource = VendorList;
});
var currentVendor = new List<Vendor>();
currentVendor.Add(new Vendor { VendorID = "111", VendorName = "aaaa" });
VendorName.SelectedItem = currentVendor;
This may not be the most efficient but you could loop to find the index and set that way.
for (int x = 0; x < VendorList.Count; x++)
{
if (VendorList[x].VendorName == currentVendor .VendorName )
{
VendorName.SelectedIndex = x;
}
}
After adding all values as list in Picker
just treat with it as an array
so if you want to set selected item just set selected item index
currentVendor.SelectedIndex = 0;
zero means you make selected item is the first one you added to Picker
If you are using MVVM, and want to set SelectedItem from the view model, things get tricky. There seems to be a bug in Xamarin that prevents us from using SelectedItem with a two way binding. More info: Xamarin Forms ListView SelectedItem Binding Issue and https://xamarin.github.io/bugzilla-archives/58/58451/bug.html.
Luckily, we can easily write our own Picker.
public class TwoWayPicker : Picker
{
public TwoWayPicker()
{
SelectedIndexChanged += (sender, e) => SelectedItem = ItemsSource[SelectedIndex];
}
public static new readonly BindableProperty SelectedItemProperty = BindableProperty.Create(
nameof(SelectedItem), typeof(object), typeof(TwoWayPicker), null, BindingMode.TwoWay, propertyChanged: OnSelectedItemChanged);
public new object SelectedItem
{
get => GetValue(SelectedItemProperty);
set => SetValue(SelectedItemProperty, value);
}
private static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (TwoWayPicker)bindable;
control.SetNewValue(newValue);
}
private void SetNewValue(object newValue)
{
if (newValue == null)
{
return;
}
for(int i = 0; i < ItemsSource.Count; i++)
{
if (ItemsSource[i].Equals(newValue))
{
SelectedIndex = i;
return;
}
}
}
}
Because is uses the same SelectedItem property, it is a drop-in replacement for Picker.
Note that if you want value equality rather than reference equality for the item class, you'll also need to override Equals like this:
public override bool Equals(object obj)
{
var other = obj as YourClass;
if (other == null)
{
return false;
}
else
{
return other.SomeValue == SomeValue; // implement your own
}
}
If you define the item class as a record instead of a class then it can select the item programmatically using the SelectedItem property.
In your case change
public class Vendor { // your class properties }
to
public record Vendor { // your class properties }
This will now work
VendorName.SelectedItem = currentVendor;

How to resolve the error 'Property is null or is not IEnumerable' in Xamarin

I'm trying to making the following
<platform:TablePrac.Columns>
<platform:TextColumn Caption="it"/>
<platform:TextColumn Caption="is"/>
</platform:TablePrac.Columns>
But when I run this the error 'Property Columns is null or is not IEnumerable' is occurred.
The code flow is following.
When I wrote like above .xaml code, the property named Columns set the value.
(Columns is defined like below)
public List<Column> Columns
{
set
{
columns = value;
SetValue(ColumnsProperty, value);
}
get
{
return (List<Column>)GetValue(ColumnsProperty);
}
}
public class Column
{
public string caption;
public Type type;
}
public class TextColumn : Column
{
public TextColumn() : base()
{
this.type = typeof(string);
}
public TextColumn(string cap) : base()
{
this.caption = cap;
this.type = typeof(string);
}
public string Caption
{
set { caption = value; }
get { return caption; }
}
public Type Type
{
get { return type; }
}
}
As a very similar case, defining StackLayout and making new views in it like below
<StackLayout>
<Label Text="it"/>
<Label Text="is"/>
</StackLayout>
is same in .cs code like below
StackLayout stack = new StackLayout
{
Children =
{
new Label { Text = "it"},
new Label { Text = "is"}
}
};
So, I want to make property Columns work as StackLayout in .xaml but I don't know how. I spend two days to solve it.... I need your help
Thank you.
(Plus, StackLayout and Children are defined like below
StackLayout
public class StackLayout : Layout<View>
Layout
[Xamarin.Forms.ContentProperty("Children")]
public abstract class Layout<T> : Layout, IViewContainer<T>
where T : View
{
public IList<T> Children { get; }
...
}
)
The problem is not IEnumerable but Null value.
When using BindableProperty in Xamarin.Forms, you can assign a default value to the Property. For example, give default value 'new List()' solve this problem.
Follwing is my code, if you have same problem, check it.
Before :
public static readonly BindableProperty ColumnsProperty = BindableProperty.Create("Columns", typeof(List<Column>), typeof(TablePrac));
public List<Column> Columns
{
set
{
SetValue(ColumnsProperty, value);
}
get
{
return (List<Column>)GetValue(ColumnsProperty);
}
}
After :
public static readonly BindableProperty ColumnsProperty = BindableProperty.Create("Columns", typeof(IEnumerable<Column>), typeof(TablePrac), new List<Column>());
public IEnumerable<Column> Columns
{
set
{
SetValue(ColumnsProperty, value);
}
get
{
return (IList<Column>)GetValue(ColumnsProperty);
}
}
I convert type of return value of Columns to IList since in case of 'StackLayout's Children', the type of Children is IList type. There is no other reason.

DevExpress - MVVM - Generate TabItems with different ViewModels

i have a DXTabControl. The DXTabItems are generated via my ViewModel.
//MainViewModel
public MainViewModel()
{
var items = new ObservableCollection<DXTabItem>();
items.Add(
new DXTabItem()
{
Header = "Test1",
Content = new WebViewModel()
});
items.Add(
new DXTabItem()
{
Header = "Test2",
Content = new CMSViewModel()
});
TabItems = items;
}
private ObservableCollection<DXTabItem> _tabItems;
public ObservableCollection<DXTabItem> TabItems
{
get { return _tabItems; }
set { SetProperty(ref _tabItems, value, () => TabItems); }
}
I am working with a DataTemplate and my TabItem is still not showing any UserControl.
//MainView.xaml
<DataTemplate x:Key="WebTemplate" DataType="{x:Type viewmodel:WebViewModel}">
<view:WebView/>
</DataTemplate>
<DataTemplate x:Key="CMSTemplate" DataType="{x:Type viewmodel:CMSViewModel}">
<view:CMSView/>
</DataTemplate>
<datatemplate:TemplateSelector x:Key="DataTemplateSelector"
WebTemplate="{StaticResource WebTemplate}"
CMSTemplate="{StaticResource CMSTemplate}" />
<dx:DXTabControl ItemsSource="{Binding TabItems}" ItemTemplateSelector="{StaticResource DataTemplateSelector}" />
//DataTemplateSelector
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate WebTemplate { get; set; }
public DataTemplate CMSTemplate { get; set; }
public override DataTemplate SelectTemplate(Object item,
DependencyObject container)
{
if (item == null) return base.SelectTemplate(item, container);
if (item.GetType() == typeof(WebViewModel))
{
return WebTemplate;
}
else if (item.GetType() == typeof(CMSViewModel))
{
return CMSTemplate;
}
else return base.SelectTemplate(item, container);
}
}
Everything is working, except showing the content i need. No view is been shown. Any idea? Did i miss something?
The following answer is based on caliburn.micro.
Step 1: Add a convention to the bootstrapper
public Bootstrapper()
{
ConventionManager.AddElementConvention<DXTabControl>(DXTabControl.ItemsSourceProperty, "ItemsSource", "DataContextChanged")
.ApplyBinding = (viewModelType, path, property, element, convention) =>
{
if (!ConventionManager.SetBindingWithoutBindingOrValueOverwrite(viewModelType, path, property, element, convention, DXTabControl.ItemsSourceProperty))
{
return false;
}
var tabControl = (DXTabControl)element;
if (tabControl.ItemTemplate == null && tabControl.ItemTemplateSelector == null && property.PropertyType.IsGenericType)
{
var itemType = property.PropertyType.GetGenericArguments().First();
if (!itemType.IsValueType && !typeof(string).IsAssignableFrom(itemType))
{
tabControl.ItemTemplate = ConventionManager.DefaultItemTemplate;
}
}
ConventionManager.ConfigureSelectedItem(element, Selector.SelectedItemProperty, viewModelType, path);
if (string.IsNullOrEmpty(tabControl.DisplayMemberPath))
{
ConventionManager.ApplyHeaderTemplate(tabControl, DXTabControl.ItemHeaderTemplateProperty, DXTabControl.ItemHeaderTemplateSelectorProperty, viewModelType);
}
return true;
};
[...]
}
Now you can bind any Screen-Collection to your DXTabControl.
Step 2: Create a collection in the ViewModel
public class MainViewModel : Screen
{
public MainViewModel()
{
DisplayName = "DevExpress Test Environment";
}
private static BindableCollection<Screen> _tbCtrl = new BindableCollection<Screen>();
public BindableCollection<Screen> TbCtrl
{
get { return _tbCtrl; }
set
{
_tbCtrl = value;
NotifyOfPropertyChange(() => TbCtrl);
}
}
}
You can e.g. put any other ViewModel which is based on the Screen class to your collection. That means, you will be able to display your content for each tabitem.
Step 3: Create the DXTabControl in your View (XAML-Code)
<dx:DXTabControl x:Name="TbCtrl" />
Give it a go. Open for feedback.
/// Alternative solution without Caliburn.Micro
Step 1: Add the DXTabControl to your MainView (XAML-Code)
<dx:DXTabControl ItemsSource="{Binding TbCtrlItems}" />
Step 2: Your MainViewModel needs to add those items like i have described above (in my question), but in this case, you have to specify the content-property
public MainViewModel()
{
_tbCtrlItems.Add(new DXTabItem()
{
Header = "Test1",
Content = new Views.View1() {DataContext = new ViewModel1()}
});
_tbCtrlItems.Add(new DXTabItem()
{
Header = "Test2",
Content = new Views.View2() { DataContext = new ViewModel2() }
});
}
private ObservableCollection<DXTabItem> _tbCtrlItems = new ObservableCollection<DXTabItem>();
public ObservableCollection<DXTabItem> TbCtrlItems
{
get { return _tbCtrlItems; }
set { SetProperty(ref _tbCtrlItems, value, () => TbCtrlItems); }
}
I hope this answer is helpful.

Categories

Resources