Im attempting to bind to the output of a method. Now I've seen examples of this using ObjectDataProvider However the problem with this is ObjectDataProvider creates a new instance of the object to call the method. Where I need the method called on the current object instance. I'm currently trying to get a converter to work.
Setup:
Class Entity
{
private Dictionary<String, Object> properties;
public object getProperty(string property)
{
//error checking and what not performed here
return this.properties[property];
}
}
My attempt at the XAML
<local:PropertyConverter x:Key="myPropertyConverter"/>
<TextBlock Name="textBox2">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource myPropertyConverter}"
ConverterParameter="Image" >
<Binding Path="RelativeSource.Self" /> <!--this doesnt work-->
</MultiBinding>
</TextBlock.Text>
</TextBlock>
my code behind
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string param = (string)parameter;
var methodInfo = values[0].GetType().GetMethod("getProperty", new Type[0]);
if (methodInfo == null)
return null;
return methodInfo.Invoke(values[0], new string[] { param });
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException("PropertyConverter can only be used for one way conversion.");
}
My problem is that I cant seem to pass the current Entity into the converter. So When i try to use reflection to get the getProperty method I have nothing to operate on
thanks, steph
Wrap the call to the method inside a get property and add this get property to whatever class that is your current DataContext.
Edit: Answering your updated question.
If you only pass one parameter to the valueconverter you don't need a multivalueconverter, just use a regular valueconverter (implementing IValueConverter). Also, why not cast the object in the valueconverter to a Distionary and use it directly instead of using reflection.
To pass current datacontext as a binding do this: <Binding . />. I'm guessing the datacontext of the textblock is entity.
Still, all this is not necessary if all you want to do is run some code before accessing a dictionary item. Just use an index property instead, you can databind to it directly:
public class Entity
{
private Dictionary<String, Object> properties;
public object this[string property]
{
get
{
//error checking and what not performed here
return properties[property];
}
}
}
<TextBlock Text="{Binding Path=[Image]}" />
Related
I have a Prism MVVM app, can easily pass one UIElement as a CommandParameter to ViewModel's Command. But now I want to pass two UIElements. Using this XAML:
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource DummyMultiConverter}">
<Binding ElementName="PasswordBoxType"/>
<Binding ElementName="PasswordBoxRetype"/>
</MultiBinding>
</Button.CommandParameter>
Using this Converter:
public class DummyMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And a simple DelegateCommand in ViewModel:
private DelegateCommand<object> _commandCloseDialogOK;
public DelegateCommand<object> CommandCloseDialogOK =>
_commandCloseDialogOK ??
(_commandCloseDialogOK = new DelegateCommand<object>(commandParameter=> CommandCloseDialogOKExecute(commandParameter)));
public virtual void CommandCloseDialogOKExecute(object commandParameter)
{
RaiseRequestClose(new DialogResult(ButtonResult.OK));
}
public virtual bool CanExecuteCommandCloseDialogOK()
{
return true;
}
When it runs - Convert method gets values correctly, as an array of 2 PasswordBoxes. But CommandCloseDialogOKExecute gets its commandParameter parameter as an array of two nulls. Same happens if I define commandParameter as object[] instead of object. What should I do that commandParameter would be array of two PasswordBoxes?
First, if you are using MVVM you really should not pass view-related data, like UIElement's, to the view model.
Assuming that you know that and this really is a simplified depiction to illustrate your issue, the problem is that the framework explicitly clears the array that is passed into the Convert method before the value that Convert returns gets applied to the binding. The result is that it applies the now-empty array (nulls):
https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Data/MultiBindingExpression.cs,1267
You can avoid this by creating another array in your Convert method:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return new []{values[0], values[1]};
}
When I try to bind a valueconverter from a defined enum Status to brush, I get an error in my XAML designer:
'OKStatus' resource not found.
The application works fine runtime, but I'm not able to see my GUI in the designer.
My resources are defined in the color.xaml file, which is read at run time.
All code is within the same namespace
My XAML:
xmlns:config="clr-namespace:App.MyNamespace"
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="c:\Skins\Colors.xaml" />
<ResourceDictionary Source="c:\Skins\Common.xaml" />
</ResourceDictionary.MergedDictionaries>
<config:StatusConverter x:Key="StateConverter" />
<config:BoolConverter x:Key="BoolConverter" />
<config:BooleanConverter x:Key="BooleanConverter" />
</ResourceDictionary>
</UserControl.Resources>
and
Status
My converter:
[ValueConversion(typeof(bool), typeof(Brush))]
public class BoolConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
bool state = (bool)value;
FrameworkElement FrameElem = new FrameworkElement();
if (state == true)
return (FrameElem.FindResource("OKStatus") as Brush);
else
return (FrameElem.FindResource("ErrorStatus") as Brush);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return null;
}
}
In this code the frameElem wont have any knowledge of the resources I have defined I guess, so I need a way to get access to my resources during design.
Is this possible?
Yes, it is possible, and your guess is correct. Resource finding starts with the logical tree, and creating a new FrameworkElement() doesn't satisfy this. It's completely disconnected.
What you can do (and what you may have to do if N8's suggestion doesn't work), is to hand your converter a reference to the UserControl as the FrameworkElement to call FindResource() on.
The reason N8's suggestion probably won't work is that Application.Current.FindResource() probably starts at application-level resources and then goes to system resources, but the resources you're after are in the UserControl's resources. If they were placed in App.xaml's resources, it would work. However, I think Application.Current may be null at design-time.
The easiest way I can think of to do this is in your UserControl's constructor:
public MyUserControl(){
var boolconv = new BoolConverter();
boolconv.FrameworkElement = this;
this.Resources.Add( "BoolConverter", boolconv );
InitializeComponent();
}
I'm pretty sure it goes before InitializeComponent(), rather than after.
Doing this in XAML would be more complicated, as you probably have to add a DependencyProperty to your converter so that you could bind the UserControl to it. I think that would be going overboard.
Another way is to put TrueBrush and FalseBrush properties on your converter and assign them in XAML, which is what I tend to do so that my converters are vague and general-use. (NB: Names are slightly different.)
<config:BoolToBrushConverter x:Key="Bool2Brush"
TrueBrush="{StaticResource OKStatusBrush}"
FalseBrush="{StaticResource ErrorStatusBrush}" />
I think the issue is that you are trying to find the resource out of a framework element not in the visual tree. Could you try the following instead?
Application.Current.FindResource("OKStatus") as Brush;
As I have learned by TechNet Wiki, there is necessary to use MultiValue Converter and MultiValueBinding to get correct registred converter and correct FrameworkElement by the UserControl.
XAML Example:
<TextBlock x:Name="tb1" Margin="20">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MyConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}"/>
<Binding Path="MyValue"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Then the converter declaration can looks :
public class MyConverter : IMultiValueConverter
{
FrameworkElement myControl;
object theValue;
public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
myControl = values[0] as FrameworkElement;
theValue = values[1];
return myControl.FindResource(">>resource u need<<");
}
public object[] ConvertBack(object value, System.Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
.....
}
}
The detail explanation is:
https://social.technet.microsoft.com/wiki/contents/articles/12423.wpfhowto-pass-and-use-a-control-in-it-s-own-valueconverter-for-convertconvertback.aspx
I have run into this problem as well. I think that calling Application.Current is the best way to get at resources from an IValueConverter, since they are not defined on a per window, or page or control, level. This would require the resources to be at least application-level, as stated above.
However, since the Application.Current reference is set to null in the designer, this method will always break the designer. What you appear to have done is given something for the designer to display, while you have given your running application access to resources in the converter.
For all of you out there with this issue, you don't need to implement the Kludge that lewi implemented; this is only if you want the designer surface to load. It does not affect your application while running as the Application.Current call has something to do.
Actually what I ended up doing (for now) was to change from FindResource to TryFindResource, and put the statements in a try/catch block.
This seems to work so far.
try
{
if (state == true)
return (FrameElem.TryFindResource("OKStatus") as Brush);
else
return (FrameElem.TryFindResource("ErrorStatus") as Brush);
}
catch (ResourceReferenceKeyNotFoundException)
{
return new SolidColorBrush(Colors.LightGray);
}
I have binding to Image.Source and TextBlocks. Also I have own convertor classes.
<conv:StatusConvertor x:Key="statusConvertor"></conv:StatusConvertor>
<conv:ImageConvertor x:Key="imageConvertor"></conv:ImageConvertor>
For example:
<Image Source="{Binding Value.profilePhoto, Converter={StaticResource imageConvertor}}" Margin="4,4,4,2"/>
<TextBlock Name="tbStatus" Text="{Binding Value.status,Converter={StaticResource statusConvertor}}" Grid.Column="0" Grid.Row="2" Margin="2,2,2,2" FontSize="11" FontWeight="Normal"></TextBlock>
I want set condition for imageConvertor, for example:
IF tbStatus.Text=="0"
THEN use imageConvertor on Image.Source
It’s possible this write in XAML, maybe in convertor class?
Instead of making your ImageConverter an IvalueConverter, make it an IMultiValueConverter:
<Image Margin="4,4,4,2">
<Image.Source>
<MultiBinding Converter="{StaticResource imageConvertor}">
<Binding Path="Value.profilePhoto" />
<Binding Path="Value.status" />
</MultiBinding>
</Image.Source>
</Image>
A IMultiValueConverter is the same as an IValueConverter, except that it passes an array of objects instead of a single object value.
public object Convert(object[] values,
Type targetType, object parameter, CultureInfo culture)
{
// Use your converter code from before, but add a check for the Status value
// as well
string path = values[0].ToString();
int status = Int32.Parse(values[1].ToString();
if (status == 0)
return newImageSource;
return DependencyProperty.UnsetValue;
}
Here is is tough for me to guess on the design of your current converter, but this gives you a rough idea on what to do. I am implying from your question that if the status is not 0, you don't want your converter to return anything - hence the DependencyProperty.UnsetValue.
I don't think it's possible to do this in XAML.
I'm pretty sure it's impossible to do (as is) in the converter because you don't have access to the sender (here a TextBlock) within.
EDIT : you can do it with a multivalue converter, because you need OneWay Binding. Multivalue converters are difficult to set up with a TwoWay binding (when you need the ConvertBack method).
What I would do would be to declare two Images (one with for each value of your TextBlock : 0 and else) and bind the visibility to the textblock Text value (or directly to Value.status).
This doesn't specifically answer the question, but I managed to solve my problem using this question as a guide, so I thought it might help future searchers. It could probably also be extended further to solve the original question with a little more work.
I was trying to find a way of evaluating an IF-conditional expression within XAML and wanted to combine the power of a Binding expression and a MarkupExtension. Using the idea of a converter, I managed to create a ConditionalMarkupConverter. This has two properties to specify the values to be returned when a binding expression evaluates to true or false, and since it's a converter, it can be easily added to a binding expression.
Converter
public sealed class ConditionalMarkupConverter : MarkupExtension, IValueConverter
{
public object TrueValue { get; set; }
public object FalseValue { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool && (bool)value)
{
// The given value has evaluated to true, so return the true value
return TrueValue;
}
// If we get here, the given value does not evaluate to true, so default to the false value
return FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
XAML
<TextBlock Text="{Binding IsActive, Converter={converters:ConditionalMarkupConverter TrueValue=Active, FalseValue=Inactive}}" />
In this example, the TextBlock binds to a boolean property called IsActive and the converter then returns the string "Active" when IsActive is true, or "Inactive" when IsActive is false.
I have a data source ('SampleAppearanceDefinitions'), which holds a single collection ('Definitions'). Each item in the collection has several properties, including Color, which is what I'm interested in here.
I want, in XAML, to display the Color of a particular item in the collection as text. I can do this just fine using this code below...
Text="{Binding Source={StaticResource SampleAppearanceDefinitions}, Path=Definitions[0].Color}"
The only problem is, this requires me to hard-code the index of the item in the Definitions collection (I've used 0 in the example above). What I want to do in fact is to get that value from a property in my current DataContext ('AppearanceID'). One might imagine the correct code to look like this....
Text="{Binding Source={StaticResource SampleAppearanceDefinitions}, Path=Definitions[{Binding AppearanceID}].Color}"
...but of course, this is wrong.
Can anyone tell me what the correct way to do this is? Is it possible in XAML only? It feels like it ought to be, but I can't work out or find how to do it.
Any help would be greatly appreciated!
Thanks!
AT
MultiBinding is your friend here:
Assuming you have a TextBlock:
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource AppearanceIDConverter}">
<Binding Source="{StaticResource SampleAppearanceDefinitions}" />
<Binding Path="AppearanceID" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
And define a MultiValueConverter to return what you wish to see:
public class AppearanceIDConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
List<item> items = (List<item>)values[0]; //Assuming its items in a List
int id = (int)values[1]; //Assuming AppearanceID is an integer
return items.First(i => i.ID == id).Color; //Select your item based on the appearanceID.. I used LINQ, but a foreach will work just fine as well
}
public object[] ConvertBack(object value, System.Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new System.NotImplementedException();
}
#endregion
}
Of course, you will need to set the converter as a resource in your Resource dictionary, like you did SampleAppearanceDefinitions. You can also ditch the multibinding and use a regular binding to AppearanceID with a IValueConverter, if you can get to the SampleAppearanceDefinitions collection through code ;).
Hope this helps
Even if it could be possible you'd better not do that this way, but instead use a dedicated property in your view model or in the code behind of your view if it has only a pure graphical meaning.
This property, say "CurrentAppearance", would expose a Color property you could bind from your Xaml :
Text="{Binding CurrentAppearance.Color}"
which is more understandable.
As a general advice : avoid to spoil your Xaml with plumbing code : Xaml should be as readable as possible,
particularly if you work with a team of designers that have no coding skills and do not want to be concerned with the way you are managing the data.
Moreover, if later you decide to change the way data are managed you would not have to change your Xaml.
MultiBinding might actually work if your list is on a viewmodel instead of a staticresource. I was suprised myself to see that the object passed on to the view is actually a pointer to the object on the model, so changing the object in the view (eg. typing in new test in the textbox) directly affects the model object.
This worked for me. The ConvertBack method is never useed.
public class PropertyIdToPropertyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length == 2)
{
var properties = values[0] as ObservableCollection<PropertyModel>;
if (properties != null)
{
var id = (int)values[1];
return properties.Where(model => model.Id == id).FirstOrDefault();
}
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Here's a basic example to explain my problem. Let's say I have
ObservableCollection<int> Numbers {get; set;}
and an IValueConverter that returns the sum of Numbers.
Normally what I'd do is changed the IValueConverter into an IMultiValueConverter and bind a second value to Numbers.Count like this
<MultiBinding Converter="{StaticResource SumTheIntegersConverter}">
<Binding Path="Numbers" />
<Binding Path="Numbers.Count" />
</MultiBinding>
However I'm unable to use this method to solve my actual problem. It seems like there should be a better way to update the binding when the collection changes that I'm just not thinking of. What's the best way to get the value converter to run when items are added and removed to Numbers?
This is actually surprisingly very difficult. An IValueConverter doesn't update, so this does not work as you'd hope.
I wrote a sample on the Microsoft Expression Gallery called Collection Aggregator that shows a working, if convoluted, approach to making this work via a Behavior that does the aggregation (Count, in your case, although I also support Sum, Average, etc) for you, instead of a converter.
In your model, subscribe to CollectionChanged and raise PropertyChanged:
Numbers.CollectionChanged += (o,e) =>
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Numbers)));
And, as thomasgalliker mentioned, you should unsubscribe from the event when the model containing the connection is no longer used.
I ended up doing something like this which seems to work. It's far from an optimal solution and I'd still be interested in something better but it seems to work for my purposes.
class CollectionChangedHandlingValueConverter : IValueConverter
{
DependencyObject myTarget;
DependencyProperty myTargetProperty;
//If this ever needs to be called from XAML you can make it a MarkupExtension and use ProvideValue to set up the Target and TargetProperty
public CollectionChangedHandlingValueConverter(DependencyObject target, DependencyProperty dp)
{
myTarget = target;
myTargetProperty = dp;
}
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
INotifyCollectionChanged collection = value as INotifyCollectionChanged;
if (collection != null)
{
//It notifies of collection changed, try again when it changes
collection.CollectionChanged += DataCollectionChanged;
}
//Do whatever conversions here
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
void DataCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if ((myTarget != null) && (myTargetProperty != null))
{
BindingOperations.GetBindingExpressionBase(myTarget, myTargetProperty).UpdateTarget();
}
}
}
And I ended up synchronizing collection (original with converter), take a look at the buttom of my post for example:
http://alexburtsev.wordpress.com/2011/03/05/mvvm-pattern-in-silverlight-and-wpf/