I've struggled to understand this error for some time and now it's finally breaking something. Here's a dumbed-down sketch of what I'm doing:
My datawrapper class:
class DummyWrapper<T> : INotifyPropertyChanged
{
public DummyWrapper() { }
private T _data;
public T Data
{
get { return _data; }
set
{
_data = value;
Notify("Data");
}
}
public static implicit operator T(DummyWrapper<T> d)
{
return d._data;
}
...
}
My XAML:
<DockPanel>
<ContentPresenter Name="cp" Visibility="Collapsed"/>
<Rectangle Name="foo" Fill="{Binding ElementName=cp, Path=Content}" Height="50" Width="50"/>
</DockPanel>
The pertinent bit of my codebehind:
DummyWrapper<Brush> dw = new DummyWrapper<Brush>(new SolidColorBrush((Color)ColorConverter.ConvertFromString("Red")));
cp.Content = dw;
And of course if this was all working the way I expected I wouldn't be here. The output window shows me this:
System.Windows.Data Error: 23 : Cannot convert 'WpfTestApplication.DummyWrapper`1[System.Windows.Media.Brush]'
from type 'DummyWrapper`1' to type 'System.Windows.Media.Brush'
for 'en-US' culture with default conversions; consider using Converter property of Binding.
... and it goes on in that vein for some time.
Is there something I can do to allow my DummyWrapper to be converted automatically (ie w/o supplying a converter in the binding) in this context?
Change your line of code to
var solidColorBrush = new SolidColorBrush((Color) ColorConverter.ConvertFromString("Red"));
DummyWrapper<Brush> dw = new DummyWrapper<Brush>(solidColorBrush);
cp.Content = (Brush)dw;
You don't want to use converters that's fine. Content is an object and doing an implicit operator will not just do that unless the Content is of type Brush. You have to explicitly cast it to Brush
System.Object is the type of the Content property.
Through inheritance there's already an implicit conversion to the base
type
Related
This seems to be a tricky one.
You have a data object which you bind to a number of user controls.
Simplified:
public class Shift : INotifyPropertyChanged
{
private int _baseColor;
public int BaseColor
{
get { return _baseColor; }
set
{
_baseColor = value;
OnPropertyChanged("BaseColor");
}
}
private Employees_E _employee;
public Employees_E Employee
{
get
{
return _employee;
}
set
{
_employee = value;
OnPropertyChanged("Employee");
}
}
And here is the deal:
Depending on the Employee, the user control will change its background. One way to solve that (which works fine) is of course to use a converter, like...
Background="{Binding Employee, ElementName=uc, Converter={StaticResource EmployeeValueConverter}
But when all of it is dynamic it complicates things a lot. I don't know in advance the number of, or the name of the Employee, or the associated color to the employee.
What I want is my user control to bind to a Dictionary so that Shift.BaseColor binds to the value where the key is Employee. Something like:
Background="{Binding BaseColor, ElementName=anynamespace, Converter={StaticResource AnotherConverter}
Or preferable:
Background="{Binding MyDictionary[Employee].Value... with a converter...
The user will be able to change the associated color without changing the data object so I need another way to update my user controls when the color change.
The value will be an integer and in the converter I return a LinearGradientBrush form a list, so the integer will be an index in that list.
UPDATE 1
The background i will change is a Border background where Multibinding isn't possible? I have found another thread showing how to do it with multibinding but doesn't work in this case...?
UPDATE 2
The problem isn't how to get access to the values or the converter. The problem is when the user change the value in the dictionary and how to properly bind that to make the user control update it's background. Let's say we have a key=MARIA and a value of 21 which is the index of my List. Then the user control has a certain color. But the user might want to associate another color to that key, but there I can't figure out how to bind it.
<UserControl x:Class="SPAS.Controls.ShiftControl" ...
<Border BorderThickness="1" x:Name="myBorder" Background="{Binding BaseColor, ElementName=uc, Converter={StaticResource BaseColorLinear}, Mode=TwoWay}" >
...
</Border>
</UserControl>
public partial class ShiftControl : UserControl
{
public static DependencyProperty BaseColorProperty = DependencyProperty.Register("BaseColor", typeof(int), typeof(ShiftControl), new UIPropertyMetadata(0));
public int BaseColor
{
get { return (int)GetValue(BaseColorProperty); }
set { SetValue(BaseColorProperty, value); }
}
public static DependencyProperty EmployeeProperty = DependencyProperty.Register("Employee", typeof(Employees_E), typeof(ShiftControl), new PropertyMetadata(Employees_E.VAKANT));
public Employees_E Employee
{
get { return (Employees_E)GetValue(EmployeeProperty); }
set { SetValue(EmployeeProperty, value); }
}...
I want the BaseColor to come from
Dictionary<Employees_E, int>
so the user can change either the Employee property or the value in the dictionary.
I am working on an application that needs to be able to manipulate shapes and lines in WPF. My original thought was to databind a collection to ListBox and use Rectangles in the datatemplate, setting each of the fill properties to the image. This has worked well for the majority of shapes, except for circles and a few rectangles. Since re-sizing an image causes pixelation and the lines to change sizes, the result is less than stellar.
I have spent some time browsing SO and a few other sites regarding Path elements, but haven't found anything that really meets my needs. My guess is I will need to generate paths differently for each type of shape and databind them using a converter similar to Path drawing and data binding or use http://www.telerik.com/help/wpf/raddiagram-overview.html or similar rad tool.
My questions: Is there an easier way of accomplishing this or any other examples?
EDIT: I also need to be able to add text. Not sure how I can do that with a path...maybe a ContentControl?
You can draw all manner of shapes by databinding a Path.Data to a Geometry. You can generate the Geometry from a list of points. A converter is perfect for this adaptation.
For example, I draw spirals by databinding the Path.Data property to a StreamGeometry which I generate off of a list of points managed by the view model, and it works quite well for my needs:
// ViewModel ...
public class ViewModel
{
[Notify]
public IList<Point> Points { get; set; }
}
// Converter ...
public class GeometryConverter : IValueConverter
{
public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture)
{
if (value == null || value == DependencyProperty.UnsetValue)
{
return value;
}
var points = (IList<Point>)value;
var i = 0;
var newPath = new StreamGeometry();
using (var context = newPath.Open())
{
var begun = false;
for (var i = 0; i < points.Count; i++)
{
var current = points[i];
if (!begun)
{
begun = true;
context.BeginFigure(current, true, false);
}
else
{
context.ArcTo(current, new Size(radius, radius), angle, false, SweepDirection.Counterclockwise, true, true);
}
}
}
newPath.Freeze();
return newPath.GetFlattenedPathGeometry();
}
}
XAML:
<Canvas>
<Path StrokeThickness="{Binding StrokeWidth}"
Canvas.Top="{Binding Top}"
Canvas.Left="{Binding Left}"
Data="{Binding Points, Converter={StaticResource GeometryConverter}}">
<Path.Stroke>
<SolidColorBrush Color="{Binding CurrentColor}" />
</Path.Stroke>
</Path>
</Canvas>
As for the text, wouldn't it be better to bind TextBlock elements and arrange those on a 'Canvas` as needed?
I have this method call:
public DataTemplate Create(Type type, string propertyName)
{
string str = #"<DataTemplate xmlns=""http://schemas.microsoft.com/client/2007"" xmlns:local=""clr-namespace:MyProjectName;assembly:MyProjectName""><StackPanel Orientation=""Horizontal""><TextBlock Text=""{Binding propertyLabel}"" FontStyle=""Italic"" Width=""120"" /><TextBox Text=""{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:MainPage}, Path=DataContext.Value1}"" Width=""120"" /></StackPanel></DataTemplate>";
DataTemplate _dt = (DataTemplate)XamlReader.Load(str);
return _dt;
}
On calling this i get the next error:
Line: 56
Error: Unhandled Error in Silverlight Application
Code: 2512
Category: ParserError
Message: Failed to create a 'System.Type' from the text 'local:MainPage'.
File:
Line: 1
Position: 253
Scenario is this:
Grid -> DataContext = ViewModel
ListBox -> ItemsSource = ViewModel.MyCollection
ListBoxItem -> DataTemaplate contains { ViewModel.MyCollection.propertyLabel and ViewModel.Value1 }
What's going on? Why do I get this error? Any ideas are very welcome.
Thanks.
I also couldn't get past this problem using the 'clr-namespace:...' namespace declaration. What I had to do was to declare an xml definition mapping in my code by using the XmlnsDefintionAttribute. A convenient place to put this attribute is in AssemblyInfo.cs. Do not put it inside a namespace block.
[assembly: XmlnsDefinition(
"http://www.yourcompany.com/yourproduct/yourcomponent",
"MyProductName")] // substitute with your own namespace
You then have to substitute the clr-namespace with the URL in 'xmlns:local' XML attribute (3rd line in the sample below)
string str = #"<DataTemplate
xmlns=""http://schemas.microsoft.com/client/2007""
xmlns:local=""http://www.yourcompany.com/yourproduct/yourcomponent"">
<StackPanel Orientation=""Horizontal"">
<TextBlock Text=""{Binding propertyLabel}"" FontStyle=""Italic"" Width=""120"" />
<TextBox
Text=""{Binding DataContext.Value1
RelativeSource={RelativeSource FindAncestor, AncestorType=local:MainPage} }""
Width=""120"" />
</StackPanel>
</DataTemplate>";
This is silverlight code - but I guess that this will be same in WPF -
I have this simple classes
public Class A1
{
string str1;
public string Str_1
{
return str1;
}
}
public Class B1
{
A1 a1;
public A1 A_1
{
return a1;
}
}
I assume that B1::A1.str1 have the value "my string".
Now in the XAML I have this
<Grid x:Name="LayoutRoot" DataContext="B1">
<StackPanel>
<TextBox Text="{Binding ElementName=A1, Path=Str_1, Mode=TwoWay}"/>
</StackPanel>
</Grid>
In the the code ( xaml.cs ) i writing in the constructor this
LayoutRoot.DataContext = this;
( the B1 object is part of the xaml.cs file and the B1 is also not null and A1 is not null )
But ==> this is not working ... and the text of the textbox is not update with the text that is in A1 object.
You are using element binding but A1 is not a named element in the Xaml page.
You want Text={Binding Path=A_1.Str_1}
This means that it points to the Str_1 property of the A_1 property of the data context (your code behind class).
Please note that TwoWay is pointless here as you have no setters on your properties.
To do this properly (assuming your values will change and be required) you need to implement setters on your A_1 and Str_1 properties and implement INotifyPropertyChanged on both your class A1 & B1.
You can say Text={Binding Path=A_1.Str_1, Mode=TwoWay} I think that should work.
Also if you want to do two-way binding you need to implement INotifyPropertyChanged to let WPF know it has to refresh the UI after you updated the value in code.
GJ
Firstly your class B1 must implement INotifyPropertyChanged like this.
Then you should make a property proxy in your B1 class like this:
public string Str_1
{
get
{
return a1.str1;
}
set
{
a1.str1 = value;
this.RaisePropertyChanged("Str_1"); // INotifyPropertyChanged implementation method
}
}
And finally update your binding:
<TextBox Text="{Binding Path=Str_1}"/>
I'm working on an application where users can select between multiple sequences of on/off durations. Sequences always start with the on period and can have a varying length (but always in on/off pairs): e.g.
var sequences = new []
{
new int[] { 10, 15 }, // 10 ms on, 15 ms off
new int[] { 15, 10, 5, 10 } // 15 ms on, 10 ms off, 5 ms on, 10 ms off
};
The sequences have a maximum duration of 10 seconds and will be repeated. One special sequence defines no on/off durations: it is always on (though I might be able to change that to {1,0} or something).
Instead of displaying the numbers on screen I'd like to show a little graphical representation for the full 10 second duration (repeating shorter sequences) so the user can compare patterns. These will be displayed in a combo box that resizes with the window. For the examples above it would look something like the following (where X is a filled in background)
xx xx xx xx xx xx xx...
xxx x xxx x xxx x xxx x ...
I suppose I'll have to use a value converter (if only for the special value), but am uncertain what the best/easiest way of creating the graphs is, especially with the resize requirement and repeating the shorter sequences. A canvas, something else?
I'd greatly appreciate any tips!
I would follow this basic approach:
Write a value converter that takes each sequence and repeats the sequence into the full 10 seconds, encoding each chunk of time with a class that specifies whether the period is 'On' and the duration.
For each sequence, bind to the ItemsSource of an ItemsControl. For the ItemsPanel, use a StackPanel with Horizontal orientation. For the ItemTemplate, use a Rectangle or whatever other visual you'd like for a chunk of time, with the Width bound to the duration. You've also included a handy 'IsOn' property now so that you can easily visualize the On/Off state. Don't worry about scaling the Width at this point.
Place the ItemsControl in a ViewBox, which can be allowed to stretch to its parent container. Now you have a visual that provides the correct proportions of duration and scales with size.
Here's a bare-bones implementation (no error handling or any attempt to make it pretty):
UDPATE: Fixed a bug that didn't properly truncate repeating sequence at 10 seconds.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace TestWpf
{
public class SeqSegment
{
public bool IsOn { get; set; }
public int Duration { get; set; }
}
public class SeqConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var result = new List<SeqSegment>();
var seq = (int[]) value;
int time = 0;
int i = 0;
bool isOn = true;
while (time < 10000)
{
result.Add(new SeqSegment { Duration = Math.Min(seq[i], 10000 - time), IsOn = isOn });
isOn = !isOn;
time += seq[i];
i++;
if (i >= seq.Length)
i = 0;
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public IEnumerable<int[]> TestSequences
{
get
{
yield return new[] {10, 5000, 10, 8};
yield return new[] {500, 5000};
yield return new[] {50, 400, 30, 10};
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
}
XAML:
<Window x:Class="TestWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:TestWpf="clr-namespace:TestWpf" Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<TestWpf:SeqConverter x:Key="SeqConverter"/>
<DataTemplate x:Key="SeqSegTemplate">
<Rectangle x:Name="Rect" Width="{Binding Duration}" Fill="Blue"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsOn}" Value="True">
<Setter TargetName="Rect" Property="Fill" Value="Green"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate x:Key="SeqTemplate">
<Viewbox Height="50" Stretch="Fill">
<ItemsControl ItemsSource="{Binding Converter={StaticResource SeqConverter}}" ItemTemplate="{StaticResource SeqSegTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Height="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Viewbox>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding TestSequences}" ItemTemplate="{StaticResource SeqTemplate}"/>
</Grid>
</Window>