I am trying to use the IValueConverter to calculate a new width for a TextBlock depending on the width on a Grid. But I always get this exception:
An unhandled exception of type 'System.Windows.Markup.XamlParseException' occurred in PresentationFramework.dll
Additional information: 'Provide value on 'System.Windows.Markup.StaticResourceHolder' threw an exception.' Line number '264' and line position '76'.
I striped down the CalMeetingSize to everything but I still get the error. So I assume i making something wrong in the xaml? Can someone give me a hint?
<Grid x:Name="CalBackGround" Margin="163,30,0,0">
...
<TextBlock Height="18" Text="{Binding subject}"
Width="{Binding Path=Width,
ElementName=CalBackGround,
Converter={StaticResource CalMeetingSizeKey}}"
/>
...
<Window.Resources>
<local:CalMeetingSize x:Key="CalMeetingSizeKey"/>
</Window.Resources>
...
public class CalMeetingSize : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return 200;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Check whether you declared the Window.Resources before the first use in XAML so that it is known.
Also build you application once after adding the resource before using it helps sometimes fixing build-errors
You must also not bind to an element's Width, but always to its ActualWidth. Width is initialized to NaN and won't work here.
You need to bind to CalBackGround's ActualWidth property, not Width.
Related
I'm trying to use a Multibinding in combination with a converter with a Button control and Width property in XAML but I can't get it to work.
The converter is:
public class ColumnsToWidthConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return 40;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
It's hardcoded for 40 now, for testing purposes.
The XAML definition is:
<Button
Height="{Binding ElementName=root,Path=KeyHeight}"
FontSize="{Binding FontSize}"
Content="{Binding Display}"
Command="{Binding ElementName=root, Path=Command}"
CommandParameter="{Binding}"
Style="{StaticResource SelectedButton}">
<Button.Width>
<MultiBinding Converter="{StaticResource ColumnsToWidthConverter}">
<Binding Path="Columns"/>
<Binding Path="KeyHeight" ElementName="root"/>
</MultiBinding>
</Button.Width>
</Button>
The button is rendered from a ListView and defined in the ListView.ItemTemplate. When debugging the application, the converter is passed and the value of 40 is returned. The object[] values parameter contains the correct values passed in the MultiBinding paths. However, the width of the button is set to its content and not the 40 as in the example above.
The ColumnsToWidthConverter is defined in the parent ListView.Resources
<converter:ColumnsToWidthConverter x:Key="ColumnsToWidthConverter"/>
When I remove the MultiBinding and set the Width property to 40 in the XAML definition, the button is rendered correctly.
The root element is the usercontrol itself and KeyHeight is a DependencyProperty.
How do I set the button width using the multibinding?
The issue does not come from the multibinding but from the converter itself. When implementing a converter, you are expected to return the same type of value as expected by the control (there's no implicit conversion since you're the one implementing the converter). In this case, the Width property is a double, so you should return a value of the same type:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return 40d;
}
I am making an RPG in WPF and C#. I have movement buttons with images attached. I am trying to figure out how to change the image of the button depending on if there is a room available to move to in that direction. I have looked up converters but I am not quite sure how to implement them for my situation.
This is one example I have tried to implement that I found online:
<Button Content="{Binding MyBooleanValue, Converter={StaticResource
MyBooleanToImageConverter}}" />
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
bool v = (bool)value;
Uri path = new Uri((v ? "ImgSrcIfTrue.png" : "ImgSrcIfFalse.png"), UriKind.Relative);
return new Image()
{
Source = new System.Windows.Media.Imaging.BitmapImage(path),
Height = ...,
Width = ...,
};
}
Here is part of the code I am working on
<!-- Movement Buttons -->
<Button Grid.Row="1" Grid.Column="1"
Click="OnClick_MoveNorth">
<StackPanel>
<Image Source= "/Image/Buttons/Up.png"/>
</StackPanel>
</Button>
I already have functions for the boolean values, i am just trying to figure out how to implement a Converter to change the button image.
I have used the Boolean Visibility and hoping to do something similar.
Visibility="{Binding HasMonster, Converter={StaticResource BooleanToVisibility}}"
Better bind the Source property of an Image element in the Content of the Button:
<Button>
<Image Source="{Binding MyBooleanValue,
Converter={StaticResource MyBooleanToImageConverter}}"/>
</Button>
The converter would directly return a BitmapImage. If the image files are supposed to be assembly resources (i.e. they are part of your Visual Studio project and their Build Action is set to Resource), they must be loaded from Pack URIs:
public class BooleanToImageConverter : IValueConverter
{
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
var uri = (bool)value
? "pack://application:,,,/ImgSrcIfTrue.png"
: "pack://application:,,,/ImgSrcIfFalse.png";
return new BitmapImage(new Uri(uri));
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
You would add the converter to the Window's Resources like this:
<Window.Resources>
<local:BooleanToImageConverter x:Key="MyBooleanToImageConverter"/>
...
</Window.Resources>
I'm trying to use an specific size to the font of an label for each platform using exclusively XAML. This code works fine:
<Label x:Name="DescricaoLabel"
Grid.Row="1"
Grid.Column="0"
HorizontalTextAlignment="Start"
Text="{Binding Descricao}"
TextColor="#426d76">
<Label.Font>
<OnPlatform x:TypeArguments="Font"
Android="14"
iOS="Micro" />
</Label.Font>
</Label>
However, the tag Label.Font is marked as obsolete. I tried this:
<Label.FontSize>
<OnPlatform x:TypeArguments="x:Double"
Android="14"
iOS="Micro" />
</Label.FontSize>
But using the tag Label.FontSize I was unable to compile because of the use of the NamedSize 'Micro'.
What is the best way to use double and also NamedSize within XAML and without make use of an obsolete tag?
The type needs to be of the same type. You cannot have both named size and double together in the default one.
The NamedSize works via an inbuilt static converter. You can write your own converter, maybe taking both as string and then converting. Or use a converter with parameter and specify the value and type.
The default one doesn't handle the scenario you are trying to achieve.
public class StringToSizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double size;
if(double.TryParse(value.ToString, out size))
{
//this is a double
return size;
}
// its a named size, so convert the named size to enum
NamedSize namedSize;
if (Enum.TryParse(value.ToString, true, out namedSize))
{
return Device.GetNamedSize(NamedSize.Default, typeof(Label));
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You can read more about Converters below :
Bindings and Collections
How do I use IValueConverters?
Another alternative approach is to define a named label style in your app.cs or app.xaml.cs with the font size set and using that style in your XAML. In that case you need not use OnPlatform in XAML, it will be in your CS file.
I have label with a width of auto, that is bound to a property of type string.
<Label x:Name="ExampleLabel" Content="{Binding ExampleProperty}"Height="30" Width="Auto" >
I then have a property that is bound to the width of that label. The converter should convert the width to a negative value.
<UserControl.Resources>
<c:PositiveToNegativeConverter x:Key="PositiveToNegativeConverter"/>
</UserControl.Resources>
"{Binding ElementName=ExampleLabel, Path=Width, Converter={StaticResource PositiveToNegativeConverter}}"
I want the converter to execute when the label content changes, but it is only fired once, when the application loads.
Here is my converter
public class PositiveToNegativeConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return (double)value * -1;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return Math.Abs((double) value);
}
}
Any help is much appreciated. Thanks.
Does it work if you bind to ActualWidth instead of Width? Width is just whatever value you last assigned to the Width property, while ActualWidth is a read-only live-updated runtime value for how wide the thing really is in the UI.
I would expect this to update when you want it to:
"{Binding ElementName=ExampleLabel, Path=ActualWidth, Converter={StaticResource PositiveToNegativeConverter}}"
I have a grid with four rows:
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding DocumentsHeight}"/>
<RowDefinition Height="Auto"/> - GRIDSPLITTER
<RowDefinition Height="{Binding ApprovedDocumentsHeight}" />
</Grid.RowDefinitions>
The dynamic resizing of rows works fine, heights are binded to strings with values like "5*". But when the user uses the GridSplitter, the binding stops working, getters are not called after next notify when I want to change the size of rows. Does anybody know where is the problem?
Thanks for help.
If you are binding to anything besides a GridLength, the binding will break.
You can either bind to a GridLength property like this...
private double documentsHeight = 100;
public GridLength DocumentsHeight
{
get { return this.GridLength(this.documentsHeight); }
set { this.documentsHeight = value.Value; }
}
Also you'll need to set Mode=TwoWay on your binding.
The reusable solution would be to use a Converter...
public class DoubleToGridLengthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double len)
return new GridLength(len, GridUnitType.Pixel);
return new GridLength(0, GridUnitType.Auto);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is GridLength ? ((GridLength)value).Value : 0;
}
}
Could be lots of things. The splitter has to "take over" row/column definitions in order to change widths/heights. This may result in the binding being removed. It would take some time to spelunk the code and see exactly what is going on, but it's pointless as we already know it fails.
So it may not be possible to do what you want easily. If it were me, I'd express the functionality I want by wrapping it in a UserControl. Expose DependencyProperties on the UserControl for DocumentsHeight and ApprovedDocumentsHeight. I'd add change event handlers to these properties, then adjust the splitter position appropriately from the control's codebehind.
To do its magic the splitter adjusts the height of the row above it by setting the Height property to an explicit value. This results in a call to SetValue, which removes the binding from the property. You'd have to manually restore the binding to use a change notification to update the row size, although if you're doing this then you might just want to consider setting the Height directly instead of using a binding. Alternatively you can create your own splitter that uses SetCurrentValue on the Height property, which will not overwrite the existing binding.
So I got it working by setting the binding to a GridLength property with a two way mode. That way, the grid splitter sets the value correctly and the binding doesnt break.
In case the grid width or height has a binding to a GridLength property in the ViewModel (that implements INPC), then the correct converter is:
public class GridLengthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture) => value;
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture) => value;
}
In the View the binding is as such (here it's a row height but it can be another property):
<RowDefinition Height="{Binding TopLength, Mode=TwoWay,
Converter={StaticResource GridLengthConverter}}"/>
In the resource dictionary, where converters will be mapped to the namespace where the class GridLengthConverter is:
<converters:GridLengthConverter x:Key="GridLengthConverter" />