I am using MultiBinding for a ComboBox. One of the parameters I want to bind is the SelectedItem's SelectedName. Here SelectedName is string type.
If NOT MultiBinding, I have this that works well:
<ComboBox Background="{Binding SelectedItem.SelectedName,
RelativeSource={RelativeSource Self}, Converter={StaticResource MyConverter}}">
But in MultiBinding when I tried to bind to SelectedItem.SelectedName it reports
Unable to cast object of type 'MS.Internal.NamedObject' to type
'System.String'.
This is my code:
<ComboBox.Background>
<MultiBinding Converter="{StaticResource MyMultiBindingConverter}">
<Binding .../>
<Binding RelativeSource="{RelativeSource Self}" Path="SelectedItem.SelectedName"/> //this line fails
</MultiBinding>
</ComboBox.Background>
How can I make it correct? Thanks.
Updated information:
The ComboBox does not have a default SelectedItem. When I use MyConverter, If I have not selected an item, the breakpoint in Convert method will not be hit. After I selected an item the breakpoint is hit which is the behavior I want.
However when I use MyMultiBindingConverter the situation is completely inverse - The breakpoint will be hit on UI load and will not be hit after I selected an item.
You should check whether you get a string in your Convert method:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length < 2)
return Binding.DoNothing;
string selectedName = values[1] as string;
if (string.IsNullOrEmpty(selectedName))
return Binding.DoNothing;:
//...
}
You can't assume that the SelectedItem.SelectedName property returns a value each time the Convert method is called.
Related
I would like to bind my TextBox.Text to two different sources.
I have 2 ViewModels, one is general ViewModel and one is more specific (which is inherit from its parent).
Both ViewModels has a property called "Hotkey".
I would like to bind my TextBox.Text so it will get the value from the general ViewModel and set it to the specific ViewModel.
I tried the following:
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Center" Foreground="#000">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource test}">
<Binding Path="DataContext.Hotkey" RelativeSource="{RelativeSource AncestorType={x:Type MetroStyle:MetroWindow}}" Mode="OneWay" />
<Binding Path="Hotkey" Mode="OneWayToSource"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
It does get the value from the general ViewModel, but it doesn't set its value to the specific one (which inherits from the parent)
I believe the problem may be in the Converter you used for MultiBinding, I've just tried a simple demo and looks like that Converter should be implemented like this:
public class TestConverter : IMultiValueConverter
{
private bool justConvertedBack;
object IMultiValueConverter.Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if (justConvertedBack) {
justConvertedBack = false;
return Binding.DoNothing;
}
return values[0];
}
object[] IMultiValueConverter.ConvertBack(object value, Type[] targetTypes,
object parameter, System.Globalization.CultureInfo culture)
{
justConvertedBack = true;
return new object[] {null, value};
}
}
It happens that after the ConvertBack has been done, the Convert will be triggered and keep the Text of your TextBox unchanged (although you tried deleting/modifying it before). So we need some flag justConvertedBack here to prevent that from occurring.
Currently changing the source from the general ViewModel will change the TextBox's Text but does not update the source from the specific ViewModel. However if setting/typing some value for the TextBox's Text will update the source from the specific ViewModel but won't reflect that value back to the source from the general ViewModel. I hope that behavior is what you want.
I have a lot of controls in my XAML in the form of
TextBlock: TextBox.
So for example:
XSize(mm):25.4
YSize(mm):50.8
etc
Now when the user clicks on an option to use imperial units I want to change all textBlocks + textBoxes to something like
XSize(in):1
YSize(in):2
etc
What is the best way to go about this ?
Try to use Converters. Create a MM to Inch converter and use that converter to change the values when the user inputs a value on the certain value.
Sample usage of a converter. You have to define a static resource under the resources of your view for the controls you want to use the converter with
MainWindow.xaml
<Window.Resources>
<spikes:Convertaaa x:Key="Convertaaa" />
</Window.Resources>
<ComboBox x:Name="OptionsToChoose"/>
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource Convertaaa}">
<Binding ElementName="OptionsToChoose" Path="SelectedValue"/>
<Binding RelativeSource="{RelativeSource Self}" Path="Text"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
Converter.cs
public class Convertaaa : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
//Implement conversion here. values[0] will give you the selected option values[1] will give you the value to convert, then do a return
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
This assumes that you do know MVVM pattern in WPF and Bindings. If you are doing behind the code, then you probably want to hook up to the TextChanged of your TextBox and instantiate a new Converter Class and call the Convert method and set the Text property of the Textbox.
I have a StatusBarItem which I want to display the following message at any given moment "X/Y" where X is the number of the row of the currently selected element, and Y is the row count.
Now, if I use the Content="{Binding ElementName=lvTabela, Path=SelectedIndex}" code in xaml I am able to get the first attribute to display, but I'm not sure how I can get both.
I suppose I could always use two StatusBarItem elements next to each other, but I'd like to learn how to do this as well.
Oh, and while we're at it, how would I go about incrementing the selected index? Basically, instead of -1 to rowCount-1 I'd like it to display 0 to rowCount. I've seen people using the formatter to add additional text to their data binding, but I'm not sure how I can manipulate the data like that.
You got 2 options:
Either set the Content of the StatusbarItem a TextBlock to use StringFormat with MultiBinding, Something like:
<StatusBarItem>
<StatusBarItem.Content>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}/{1}">
<MultiBinding.Bindings>
<Binding ElementName="listView"
Path="SelectedIndex" />
<Binding ElementName="listView"
Path="Items.Count" />
</MultiBinding.Bindings>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StatusBarItem.Content>
</StatusBarItem>
or use a Converter on the MultiBinding to not have to use the TextBlock
<Window.Resources>
<local:InfoConverter x:Key="InfoConverter" />
</Window.Resources>
...
<StatusBarItem>
<StatusBarItem.Content>
<MultiBinding Converter="{StaticResource InfoConverter}">
<MultiBinding.Bindings>
<Binding ElementName="listView"
Path="SelectedIndex" />
<Binding ElementName="listView"
Path="Items.Count" />
</MultiBinding.Bindings>
</MultiBinding>
</StatusBarItem.Content>
</StatusBarItem>
and InfoConverter.cs:
class InfoConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
return values[0].ToString() + "/" + values[1].ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
StatusBarItem expects an object, when StringFormat returns a string and hence why we cannot use StringFormat with the MultiBinding without the TextBlock which can take a string in it's Text field.
As for your second question on how to increment the SelectedIndex value, you can do that with the Converter easily,
Just switch the Convert(...) function in InfoConverter.cs to
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
return (System.Convert.ToInt32(values[0]) + 1).ToString() + "/" + values[1].ToString();
}
I have a bunch of textblocks in an itemscontrol... I need to know how can I underline the text in the textblock based on whether the text is available in a list in the data model..
Sounds very simple to me...but I have been googling since the past 8 hrs...
Can I use datatriggers and valueconverters for this purpose? If yes, then how can I execute the method which lies in the viewModel (the method which helps me to check whther a given a text exists in the data model list)...
Even if I go for conditional templating....how do I access the list which lies in my model (the viewmodel can fetch it...but then how do i access the viewmodel?)..
This should be a fairly easy thing to do...Am I really missing something very simple here?? :)
I am following the MVVM pattern for my application..
One way is to use a multivalueconverter which is a class that implements IMultiValueConverter. A multivalueconverter allows you to bind to several values which means that you can get a reference to both your viewmodel and the text of your TextBlock in your valueconverter.
Assuming that your viewmodel has a method called GetIsUnderlined that returns true or false indicating whether or not the text should be underlined your valueconverter can be implemented along these lines:
class UnderlineValueConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var viewmodel = values[0] as Window1ViewModel;
var text = values[1] as string;
return viewmodel.GetIsUnderlined(text) ? TextDecorations.Underline : null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
You can use this valueconverter in the following way for a TextBlock:
<Grid x:Name="grid1" >
<Grid.Resources>
<local:UnderlineValueConverter x:Key="underlineValueConverter" />
</Grid.Resources>
<TextBlock Text="Blahblah">
<TextBlock.TextDecorations>
<MultiBinding Converter="{StaticResource underlineValueConverter}">
<Binding /> <!-- Pass in the DataContext (the viewmodel) as the first parameter -->
<Binding Path="Text" RelativeSource="{RelativeSource Mode=Self}" /> <!-- Pass in the text of the TextBlock as the second parameter -->
</MultiBinding>
</TextBlock.TextDecorations>
</TextBlock>
</Grid>
Is there a way to have another binding as a fallback value?
I'm trying to do something like this:
<Label Content="{Binding SelectedItem.Name, ElementName=groupTreeView,
FallbackValue={Binding RootGroup.Name}}" />
If anyone's got another trick to pull it off, that would be great.
What you are looking for is something called PriorityBinding (#6 on this list)
(from the article)
The point to PriorityBinding is to
name multiple data bindings in order
of most desirable to least desirable.
This way if the first binding fails,
is empty and/or default, another
binding can take it's place.
e.g.
<TextBox>
<TextBox.Text>
<PriorityBinding>
<Binding Path="LastNameNonExistant" IsAsync="True" />
<Binding Path="FirstName" IsAsync="True" />
</PriorityBinding>
</TextBox.Text>
</TextBox>
If you run into problems with binding to null values and PriorityBinding (as Shimmy pointed out) you could go with MultiBinding and a MultiValueConverter like that:
public class PriorityMultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.FirstOrDefault(o => o != null);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Usage:
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource PriorityMultiValueConverter}">
<Binding Path="LastNameNull" />
<Binding Path="FirstName" />
</MultiBinding>
</TextBox.Text>
</TextBox>
Under what conditions would you like it to use the Fallback value? How would you determine that a binding has failed? A binding is still valid even if it's bound to a null value.
I think a good bet may be to use a converter to convert to a default value if the binding returns null. I'm not sure how you could default to another bound value though.
Check out converters here