How to bind observable collection to canvas with converter - c#

I have a collection of functions that I want to plot them on canvas. So I think the correct data type to plot these functions is Polyline.
So I need a converter to convert these functions into collection of polylines and finally show them in canvas.
Here is XAML code.
<ItemsControl ItemsSource="{Binding WaveCollection,
RelativeSource ={RelativeSource FindAncestor, AncestorType={x:Type Window}},
Converter={StaticResource PlotterConverter}}" Margin="10,10,0,239" HorizontalAlignment="Left" Width="330">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="GhostWhite" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
And here is part of the converter which converts waveforms into polylines. The binding mode is one way.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var collection = value as IEnumerable<Waveform>;
return new ObservableCollection<Polyline>(GetPlots(collection));
}
However when I put break point, I notice that it only fires once when the program starts. The collection is empty yet so nothing especial happens, but after that, when I add items to collection nothing happens. No event fires. Why?
To make sure I also added this code to converter to see if it really fires or not but nothing happened.
var collection = value as ObservableCollection<Waveform>;
collection.Clear(); // this is supposed to clear collection. if binding works correct!
//...
Note that I also binded this collection into listview to show information of waves which works fine when the collection updates.
Edit:
I guess the problem is this part return new ObservableCollection<Polyline>... which changes the collection in first run and will mess up binding?

Instead of converting the whole ObservableCollection at once, use a converter that takes one item at a time.
In this case, this means that you specify the type of control each item shall be displayed as by defining the ItemsControl's ItemTemplate in XAML:
<ItemsControl ItemsSource="{Binding WaveCollection,
RelativeSource ={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Margin="10,10,0,239" HorizontalAlignment="Left" Width="330">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="GhostWhite" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Polyline Points="{Binding, Converter={StaticResource PlotterConverter}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The PlotterConverter will now be passed each item on its own, so all it needs to do is convert a Waveform object to an object of type PointCollection (since the Polyline's Points property is of type PointCollection):
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var item = value as Waveform;
return new PointCollection(GetPlot(item));
}
Of course, the GetPlot method needs to be adapted as well.

Related

xaml - how to show display value that is different from its property value without using KeyValuePair

I have a ComboBox that grabs its data from a List of strings.
Styles.Add("bold");
Styles.Add("italic");
Styles.Add("underline");
Styles.Add("");
When shown in the ComboBox, I want it to actually display: { "bold", "italic", "underline", "[Discard style]" }, respectively. It's important that I'm able to change that empty string value to display "[Discard style]" but also retain the empty string value when the SelectedChanged event fires.
Is there some way to do this (without changing the underlying data structure to support a KeyValuePair)?
This is what I originally started with:
<ComboBox ItemsSource="{Binding Styles, Mode=OneWay}" SelectedItem="{Binding SelectedStyle}" SelectionChanged="StyleComboBox_SelectionChanged" />
Since then, I've tried adding and doing many variations/combinations of
<ComboBox ItemsSource="{Binding Style, Mode=OneWay}" SelectedItem="{Binding SelectedStyle}" DisplayMemberPath="{Binding SelectedStyle, Converter={StaticResource StyleConverter}}" SelectionChanged="StyleComboBox_SelectionChanged" Height="22" Width="175" />
with SelectedValuePath={Binding SelectedStyle}, where StyleConverter is as follows:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string selectedStyle = value as string;
if (string.IsNullOrEmpty(selectedStyle))
return "[Discard style]";
return value;
}
I've mostly encountered the scenario where the display value is correct, but the underlying value is not - it's somehow always changed to "[Discard style]" as well. Do I maybe need two converters somehow? One just for displaying in the ComboBox and another just for selecting the item?
Another important note is that "[Discard style]" will be translated in the UI based on the operating system. So I can't simply just compare the string to see if it matches and turn it back into an empty string.
There are actually quite a few different ways of achieving this, but for something this simple I'd probably just use a DataTemplate with a DataTrigger to change the text when it's an empty string:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
<ComboBox ItemsSource="{Binding Styles, Mode=OneWay}" SelectedItem="{Binding SelectedStyle}">
<ComboBox.Resources>
<DataTemplate DataType="{x:Type sys:String}">
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding}" />
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="">
<Setter Property="Text" Value="[Discard style]" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ComboBox.Resources>
</ComboBox>
You will, however, need to use a converter if you want to handle null in your string list. In that case ignore the code above and apply the converter to the ItemsSource binding:
<Window.Resources>
<conv:StringConverter x:Key="StringConverter" />
</Window.Resources>
<StackPanel Orientation="Vertical">
<ComboBox ItemsSource="{Binding Styles, Mode=OneWay, Converter={StaticResource StringConverter}}" SelectedItem="{Binding SelectedStyle}" />
</StackPanel>
And then your converter would just look something like this:
public class StringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value as IEnumerable<string>).Select(s => String.IsNullOrEmpty(s) ? "[Discard style]" : s);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
UPDATE: Oh ok, I see what you're trying to do. In that case yes, you need to use two converters....one to filter the list of strings that the view model is passing to the view, and then a second to change the value being propagated back again.
Before I go any further I should point out that what you're trying to do is a really, really bad idea. The whole point of MVVM is that logic is done in the view model. What you're effectively trying to do is implement logic in the view layer, and that's completely against the whole MVVM/data-binding paradigm. The whole purpose of the view model is to prepare data in a format that the view can readily consume with as few changes as possible; the minute you find yourself putting logic in the view (including converters, which are also part of that layer) it's a very strong sign that your view model is not doing its job properly.
To answer your question though, you would need a converter in your SelectedStyleBinding in addition to the one I posted above. You would now also be forced to make that binding one-way-to-source, which means you lose the ability to programatically control the currently selected item:
<ComboBox ItemsSource="{Binding Styles, Mode=OneWay, Converter={StaticResource StringConverter}}" SelectedItem="{Binding SelectedStyle, Converter={StaticResource SingleStringConverter}, Mode=OneWayToSource}" />
And the second converter:
public class SingleStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value?.ToString() == "[Discard style]") ? "" : value?.ToString();
}
}
If you need to retain programatic item selection then your only other option is to bind to SelectedIndex instead of SelectedItem, your view model would then have to be responsible for looking the string up in the source list.
But again, all of this is very bad code. The correct thing to do here is to modify your source list to be a custom view model class or something else your view can more readily work with.

Datagrid ObservableCollection for one row wpf

I want to populate a datagrid with itemsource={Binding Model}.
This is not working out. It seems as the datagrid does not understand how to display these properties.
An easy but silly workaround works great:
In viewmodel:
Props= new ObservableCollection<MonitoringBinaryModel>();
_Model = new MonitoringBinaryModel(name);
Props.Add(_Model);
Then in xaml
itemsource={Binding Props}
Seems silly to create an observablecollection when its only suppose to hold one item. Is there a better way to make any type of instance observable?
DataGrid is designed to display a collection of objects of same type. Collection is a must. If you want DataGrid to show a content of your model, you need to obey former's design, by either using ObservableCollection or implementing a bunch of interfaces which would allow your viewmodel's properties to be retrieved in 'collection way'.
I used to have a bunch of models implementing ITypedList interface back in Windows Forms time - it wasn't a simple exercise to say the truth, so if I were you I'd rather go for either way:
Wrap model into any collection - exactly as you've stated
Replace data grid with container grid plus a number of direct bindings, like this:
<Grid>
<TextBlock Grid.Column="2" Grid.Row="0" Text="Prop1"/>
...
<TextBlock Grid.Column="2" Grid.Row="1" Text="{Binding Prop1}"/>
Well ItemsSource property is of type IEnumarable so until your MonitoringBinaryModel implement IEnumerable binding will not work.
Again because ItemsSource is IEnumerable you should provide IEnumerable as binding source. So there is no need to make yout Props as ObservableCollection. You can use ordinary List<>, or anything implementing IEnumerable with your single MonitoringBinaryModel:
_Model = new MonitoringBinaryModel(name);
Props = new List<MonitoringBinaryModel> { _Model };
Other option is to use CompositeCollection inside your XAML:
<DataGrid.ItemsSource>
<CompositeCollection>
<Binding Path="_Model"/>
</CompositeCollection>
</DataGrid.ItemsSource>
reusable wrapper via converter:
public class ItemsSourceConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// doesn't allow to add new rows in DataGrid
return Enumerable.Repeat(value, 1);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
usage in xaml
add converter to resourses:
<Window.Resources>
<wpfApplication1:ItemsSourceConverter x:Key="ItemWrapper"/>
</Window.Resources>
and use converter resourse with binding
<DataGrid ItemsSource="{Binding Path=Model, Converter={StaticResource ItemWrapper}}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Name}"/>
</DataGrid.Columns>
</DataGrid>
or
<ItemsControl ItemsSource="{Binding Path=Model, Converter={StaticResource ItemWrapper}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

C# WPF: Listbox with drag to select text

I need to create a WPF ListBox that supports two features:
Content Converter Binding:
The items in the ListBox need to be passed to a converter that converts the items to a text format.
Display items in a way that lets users select and copy text from ListBox items
I need the text of each ListBox item to be selectable. Users want to use their mouse to drag-to-select parts of the elements so they can copy the text to their clipboard.
I implemented [this copy/paste solution][1] but it does not let a user select parts of the ListBox item text, rather it supports copying the entire text.
I'm able to create a ListBox using the converter, but I can not figure out how to put the converted text into a control that lets users select the displayed text. Here is what I have:
<ListBox Name="FinishedTestErrorsListBox"
FontSize="12"
ItemsSource="{Binding Path=SelectedComparisonResult.TestFailItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding Converter={StaticResource testFailItemConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I've tried adding a TextBox to the DataTemplate as shown below...
<TextBlock Text="{Binding Converter={StaticResource testFailItemConverter}}"/>
... but this creates a runtime error caused by sending the wrong type of object to the converter. I know here I'm not setting up the converter binding properly, though I don't have a good grasp on how I should setup the binding here or why this causes errors.
So, my question is:
What content container can I use to let users select text from the individual ListBox items?
Thank you for any help,
Charlie
EDIT
Here's the converter code...
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ITestFailItem i = (ITestFailItem)value;
return i.Itemize();
}
EDIT 2
The following runtime error is throw when the ListBox is first initialized:
An unhandled exception of type 'System.Windows.Markup.XamlParseException' occurred in PresentationFramework.dll
Additional information: Provide value on 'System.Windows.Baml2006.TypeConverterMarkupExtension' threw an exception
EDIT 3
The culprit is a line of code I'd omitted from the original snippet as I thought it was irrelevant - I've learned a good lesson along the way!
Extension Question
Why does the following snippet cause an error? How can I achieve the desired affect of making the textbox span the entire containing grid?
<TextBox Width="*"
Text="{Binding Path=., Converter={StaticResource testFailItemConverter}}"/>
Try this. TextBlocks don't support text selection, but TextBoxes do. You just have to make it read-only so the user can't modify the text, and change its border thickness and background so they look like labels:
<ListBox Name="FinishedTestErrorsListBox"
FontSize="12"
ItemsSource="{Binding Path=SelectedComparisonResult.TestFailItems}">
<ListBox.Resources>
<converter:TestFailItemConverter x:Key="testFailItemConverter" />
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=.,
Converter={StaticResource testFailItemConverter},
Mode=OneWay}"
BorderThickness="0"
Background="Transparent"
IsReadOnly="True"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Have You tried TextBox? You can select text inside textbox. Path have to be changed to Path=.
<TextBox Text="{Binding Path=., Converter={StaticResource testFailItemConverter}}" />
There is not much code to work with, but this code works for me:
xaml:
<Window x:Class="StackOverflowTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:StackOverflowTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<s:TestFailItemConverter x:Key="testFailItemConverter" />
</Window.Resources>
<Grid>
<ListBox Name="FinishedTestErrorsListBox"
FontSize="12"
ItemsSource="{Binding Path=SelectedComparisonResult.TestFailItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<!--<ContentControl Content="{Binding Converter={StaticResource testFailItemConverter}}"/>-->
<TextBox Text="{Binding Path=., Converter={StaticResource testFailItemConverter}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Model's code:
public class Dummy
{
public ObservableCollection<string> TestFailItems { get; set; }
public Dummy()
{
TestFailItems = new ObservableCollection<string>(new List<string> { "a", "b" });
}
}
public class Model
{
public Dummy SelectedComparisonResult { get; set; }
public Model()
{
SelectedComparisonResult = new Dummy();
}
}
Converter's code:
public class TestFailItemConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return "aa";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}

Displaying an Image from a file in an Image control in WPF

I have a huge amount of images (10000 +) all of which I know are JPEGS (from the file header of FF D8)
basically I search a database which gives me matching filenames for the search. I then want to show these images in a list view.
my problem is that none of the images have extensions and when I try to load the image into the list view I get the following error:
No imaging component suitable to complete this operation was found
I believe this is because it cant determine the file type to open it.
I am attempting to bind to a path property using a value converter to return the BitmapImage which will be displayed in the list view Image control.
the converter is here:
public class PathToImageSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (File.Exists(value.ToString()))
{
//these files are JPEGs but without an extension
return new BitmapImage(new Uri(value.ToString()));
}
else return new BitmapImage();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The WPF control in XAML is here:
<!-- The list view containing the images, bound to the logos collection in the background -->
<ListView x:Name="lstImages"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding Shoeprints}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedIndex="{Binding SelectedImageIndex}">
<!-- Use a wrap panel so that the images appear side by side instead of one in each row -->
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<!-- Sets the template for the data to be displayed -->
<ListView.ItemTemplate>
<DataTemplate>
<!-- Defines the actual image being displayed -->
<Image Width="50"
Height="50"
Margin="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding ShoePrintImagePath, Converter={StaticResource PathToImageSourceConverter}}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
and ShoePrintImagePath is a string property in the ShoePrints class. I know that the rest of this works as I have successfully bound to other properties in this class in this list vew but I cant work this out for images.
C#
VS 2012
WPF
Xaml

Binding to instance of ItemsSource

I have a ListBox on a WPF User Control that is defined as
<ListBox Grid.Row="1" ScrollViewer.CanContentScroll="False" Background="#00000000" BorderThickness="0" ItemsSource="{Binding BuyItNowOptions}"
ItemTemplate="{DynamicResource BuyItNowDataTemplate}" IsSynchronizedWithCurrentItem="True"
Style="{DynamicResource InheritEmptyListStyle}" SelectedItem="{Binding SelectedResearch}" ItemContainerStyle="{DynamicResource ListBoxItemStyle}"/>
BuyItNowOptions is a public property on the ViewModel that is of type ObservableCollection
In the BuyItNowDataTemplate I have a label that needs to have some logic performed before displaying a price.
<Label Padding="1" HorizontalContentAlignment="Stretch" Grid.Column="2" Grid.Row="2" Margin="1">
<TextBlock Text="{Binding ExchangePrice, StringFormat=C}"
Visibility="{Binding ReturnRequired, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Label>
The binding here indicates that it will use the ExchangePrice property of the instance of AutoResearchProxy that it is on like BuyItNowOptions[CurrentIndex].ExchangePrice.
What I would like to know is it possible to create the binding in such a way that it references the whole instance of the AutoResearchProxy so that I can pass it to a converter and manipulate several properties of the AutoResearchProxy and return a calculated price?
I would envision my converter looking like this.
public class PriceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is AutoResearchProxy)
{
var research = value as AutoResearchProxy;
//Some logic to figure out actual price
}
else
return String.Empty;
}
Hopefully this makes sense.
You can pass the whole datacontext-object to a Binding by not specifying a Path or by setting it to ., that however will result in the binding not updating if any of the relevant properties of that object change.
I would recommend you use a MultiBinding instead, that way you can target the necessary properties and the binding will update if any of those change. (For usage examples see the respective section on MSDN)
MyProperty="{Binding Converter={StaticResource ObjectToDerivedValueConverter}
That should do it.

Categories