I am making a Bing Map program using C# WPF MVVM.
Whenever the map view in Bing Map changes, I want to bind a BoundingRectangle to the ViewModel and save it.
But BoundingRectangle is just a setter.
So, I am trying to connect an event rather than a general Command binding and send it as a CommandParameter.
I wrote below
Xaml
<Grid Grid.Row="0" Grid.Column="1">
<bm:Map x:Name="bm"
Margin="0,10,10,10"
CredentialsProvider="..."
Mode="AerialWithLabels"
BorderBrush="{StaticResource DefalutBorderBrush}"
BorderThickness="1"
Center="{Binding CenterLocation, Mode=TwoWay,Converter={StaticResource Converter}}"
ZoomLevel="{Binding CurrentZoomLevel, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="ViewChangeOnFrame">
<i:InvokeCommandAction Command="{Binding ViewChangedCommand, Mode=OneWay, Converter={StaticResource TestConverter}}"
CommandParameter="{Binding BoundingRectangle, Mode=OneWay, ElementName=bm, Converter={StaticResource ConverterRect}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</bm:Map>
View Model
public class TestViewModel
{
private double[] _boundingBox;
public double[] BingMapBoundingBox
{
get
{
return _boundingBox;
}
set
{
SetProperty(ref _boundingBox, value);
}
}
public ICommand ViewChangedCommand
{
get;
set;
}
public TestViewModel()
{
ViewChangedCommand = new RelayCommand<double[]>(ViewChanged);
}
public void ViewChanged(double[] boundingBox)
{
BingMapBoundingBox = boundingBox;
}
}
Converter
public class LocationRectToDoubleArrayConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double[] resultArry = new double[4] { 0, 0, 0, 0 };
if (value == null)
return resultArry;
LocationRect currLocation = value as LocationRect;
resultArry[0] = currLocation.Northwest.Longitude;
resultArry[1] = currLocation.Northwest.Latitude;
resultArry[2] = currLocation.Southeast.Longitude;
resultArry[3] = currLocation.Southeast.Latitude;
return resultArry;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
LocationRect resultLocation = new LocationRect();
if (value is null)
return resultLocation;
double[] dblArray = value as double[];
resultLocation.Northwest.Longitude = dblArray[0];
resultLocation.Northwest.Latitude = dblArray[1];
resultLocation.Southeast.Longitude = dblArray[2];
resultLocation.Southeast.Latitude = dblArray[3];
return resultLocation;
}
}
The event is executed every time the View is changed, but CommandParameter is executed only once when the program is first executed, and is not executed after that.
Whenever the View changes, I want the BoundingRectangle to come in as a CommandParameter. What should I do?
Sorry for the lack of explanation as English is not my native language.
Thank You
Related
I've got a Observable collection of custom objects and a public dictionary variable.
I would like the "BrandName" attribute to act as the Key for the "Brands" dictionary and bind the colour to the button. How would I go about doing this? The dictionary variable is outside of the class.
C# Code:
private ObservableCollection<BusService> BusServicesGUI;
public Dictionary<String, Brush> Brands;
public MainWindow(Dictionary<String, BusService> busServices)
{
InitializeComponent();
BusServicesGUI = new ObservableCollection<BusService>(BusServices.Values);
lstMachineFunctions.ItemsSource = BusServicesGUI;
lstMachineFunctions.Items.Refresh();
}
C# Class:
public class BusService
{
public string ServiceNumber { get; set; }
public string BrandName { get; set; }
public List<Location> Locations { get; set; }
public BusService(string brandName, string serviceNumber)
{
BrandName = brandName;
ServiceNumber = serviceNumber;
Locations = new List<Location>();
}
}
XAML CODE:
<StackPanel x:Name="ServiceStack">
<ItemsControl x:Name="lstMachineFunctions">
<ItemsControl.ItemTemplate >
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<usercontrols:BusServiceCard/>
<Button Tag="{Binding ServiceNumber}" Background="{Binding Brands[BrandName]}" Height="50" Click="ButtonCl"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
As you can see from the XAML my current attempts have been trying Background="{Binding Brands[BrandName]}" This has however not worked, any help would be much appreciated.
You can use an IValueConverter to pefrom this operation.
public class BrandColorConverter : IValueConverter
{
public Dictionary<String, Brush> Brands = new Dictionary<string, Brush>()
{
{ "brand1", Brushes.Red },
{ "brand2", Brushes.Blue }
};
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (!(value is BusService))
return Binding.DoNothing;
var busService = (BusService)value;
if (!Brands.ContainsKey(busService.BrandName))
return Binding.DoNothing;
return Brands[busService.BrandName];
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
In xaml, add it as a static resuorce:
<Window.Resources>
<local:BrandColorConverter x:Key="BrandColorConverter"/>
</Window.Resources>
And use it in your button:
<Button Tag="{Binding ServiceNumber}"
Background="{Binding Converter={StaticResource BrandColorConverter}}"
Height="50"
Click="ButtonCl"/>
This binding goes to the current element, so the whole BusService object will be passed to the converter.
Hope it solves your problem.
I would strongly advise you to look into MVVM pattern if you are going to use WPF with data binding, as it makes things much more streamlined.
i have a problem converting a string to a icon. The icon Geometry is in a ResourceDictionary. The ValueConverter is not called (i tried to debug in the Convert Method of the Converter). Here is my code:
xaml:
<Window.Resources>
<local:StatusToPathDataConverter x:Key="PathConverter"/>
</Window.Resources>
<Grid>
<Path Width="20"
Height="20"
Stretch="Uniform"
Fill="Black"
Data="{Binding Path=Status,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource PathConverter}}"/>
</Grid>
cs:
public partial class MainWindow :Window {
public MainWindow() {
InitializeComponent();
}
public string Status
{
get { return (string)GetValue(StatusProperty); }
set { SetValue(StatusProperty, value); }
}
public static readonly DependencyProperty StatusProperty =
DependencyProperty.Register("Status", typeof(string), typeof(MainWindow));
}
public class StatusToPathDataConverter :IValueConverter {
private static ResourceDictionary iconDictionary;
public ResourceDictionary IconDictionary
{
get
{
if(iconDictionary == null) {
iconDictionary = new ResourceDictionary();
iconDictionary.Source = new Uri("/WPFBindingTest;component/Resources/IconDictionary.xaml", UriKind.RelativeOrAbsolute);
}
return iconDictionary;
}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var status = (string)value;
if(statinStatus == null)
return null;
switch(status.ToLower()) {
case "test":
return IconDictionary["TestIcon"];
// ...
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
You're not binding to anything. You need to tell the Binding to go to the Window to find the Status property. The recommended way to do that is to use RelativeSource, as below:
<Path
Width="20"
Height="20"
Stretch="Uniform"
Fill="Black"
Data="{Binding Path=Status,
RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource PathConverter}}"
/>
As #Clemens notes, UpdateSourceTrigger=PropertyChanged doesn't make any sense on this binding and should not be there. That attribute tells the Binding when it should update the binding's source property. The source property is Window.Status, in this case.
However, the Path.Data property does not update the property it's bound to. A Path displays a Geometry; it doesn't edit a Geometry. UpdateSourceTrigger exists for control properties that update viewmodel properties, like TextBox.Text. That's the most common use for UpdateSourceTrigger=PropertyChanged: By default TextBox.Text updates the source property when the TextBox loses focus, but sometimes you want it to update on each keystroke.
Set the DataContext of the window to itself for the binding to work and the Convert method of the converter to get called:
public MainWindow() {
InitializeComponent();
DataContext = this;
}
If the binding to the source property fails the converter will never be invoked.
I created a ComboBox listing the colors that System.Windows.Media.Colors predefines, using the approach told in this question: How can I list colors in WPF with XAML?
My XAML code now is:
<Window ...>
<Window.Resources>
<ObjectDataProvider
ObjectInstance="{x:Type Colors}" MethodName="GetProperties" x:Key="ColorList" />
<local:StringToBrushConverter x:Key="FontColorConversions" />
</Window.Resources>
<Grid Background="Black">
...
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource ColorList}}"
SelectedValue="{Binding FontColor, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectedValuePath="Name">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Foreground" Value="{Binding Converter={StaticResource FontColorConversions}}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
...
</Grid>
</Window>
And besides, please note that I bind SelectedValue to a VM class's FontColor property, which is of string type.
class FontSetting : INotifyPropertyChanged
{
private string _fontColor = "Lavender"; // initial color
public event PropertyChangedEventHandler PropertyChanged;
public string FontColor
{
get
{
return _fontColor;
}
set
{
_fontColor = value;
OnPropertyChanged("FontColor");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And I set the DataContext of the Window containing this ComboBox to an instance of FontSetting.
So each item in the ComboBox actually display a string representing a certain color now, what I want to do is set an item's Foreground color to that color its content indicates, like this:
Can anyone help? Thanks.
UPDATED:
Since most of the solutions have a converter which converts string to Brush and actually I already have it, now I want to put mine here, as I binded a TextBox's Foreground to FontSetting's FontColor property, so that when you change the ComboBox, the color of that TextBox changes accordingly.
Here is my converter class, and it works fine by now:
class StringToBrushConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
BrushConverter conv = new BrushConverter();
SolidColorBrush brush = conv.ConvertFromString("Lavender") as SolidColorBrush;
if (null != value)
{
brush = conv.ConvertFromString(value.ToString()) as SolidColorBrush;
}
return brush;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
When I click the ComboBox to open the dropdown list, I got an exception:
CONCLUSION
Amine's solution works, it's my mistake. I explain briefly now, if you bind a ComboBox to System.Windows.Media.Colors like what I am doing, when the item is rendered, the Convert() method of the converter class (which you assign to the binding) is executed, and actually the value passed to Convert() as its first parameter is a Syetem.Windows.Media.Color instance. I made mistake coz I thought it was of string type.
Therefore, in my case I need two converter classes, one converting string to Brush, and the other one converting Color to Brush. So I will keep my own StringToBrush converter and add Amine's ColorToBrush converter.
However, I simplified Amine's implementation a bit:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
BrushConverter conv = new BrushConverter();
SolidColorBrush brush = SolidColorBrush)conv.ConvertFromString(FontSetting.DEFAULT_FONT_COLOR);
if (null != value)
{
PropertyInfo pi = value as PropertyInfo;
if (null != pi)
{
brush = conv.ConvertFromString(pi.Name) as SolidColorBrush;
}
}
return brush;
}
Moreover, Joe's input is also valuable, put all them together, I can keep the items' color consistent, which is perfect.
You can set Style of ComboBoxItem as bellow :
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240" x:Name="CB"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
DisplayMemberPath="Name"
SelectedValuePath="Name">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Foreground" Value="{Binding Converter={StaticResource converter}}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
By using this converter:
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
object obj = ((System.Reflection.PropertyInfo)value).GetValue(this,null);
return (SolidColorBrush)new BrushConverter().ConvertFromString(obj.ToString());
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return value;
}
}
Two ways, use a value converter or an intermediate property. The easiest is probably an intermediate property as you are using well structured bindings already for your SelectedItem (but value converters are fun too!).
SelectedValue is bound to FontColor, so in your setter set another value:
public string FontColor
{
get
{
return _fontColor;
}
set
{
_fontColor = value;
ForegroundColorToDisplay = GetBrushFromColorString(value);
OnPropertyChanged("FontColor");
}
}
public Brush _foregroundColorToDisplay
public Brush ForegroundColorToDisplay
{
get
{
return _foregroundColorToDisplay;
}
set
{
_foregroundColorToDisplay= value;
OnPropertyChanged("ForegroundColorToDisplay");
}
}
or, if you don't want to store it:
public string FontColor
{
get
{
return _fontColor;
}
set
{
_fontColor = value;
//note it fires two changed events!
OnPropertyChanged("ForegroundColorToDisplay");
OnPropertyChanged("FontColor");
}
}
public Brush ForegroundColorToDisplay
{
get
{
return GetBrushFromColorString(value);;
}
}
You can bind to this new property in your xaml:
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
SelectedValue="{Binding FontColor, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
Foreground="{Binding ForegroundColorToDisplay, Mode=OneWay}"/>
If you are interested, a value converter would work like this:
Create a class in your code behind (or elsewhere if it's going to be used a lot) that implements IValueConverter, takes the string and returns a Brush:
public class StringToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return GetBrushFromString(value as string)
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
Throw new SomeException();
}
}
and use it in your binding in xaml to convert the selectedItem binding to Foreground brush:
<Window.Resources>
<local:StringToBrushConverter x:Key="converter" />
</Window.Resources>
...
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
SelectedValue="{Binding FontColor, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
Foreground="{Binding FontColor, Mode=OneWay, Converter={StaticResource converter}}"/>
So I have a button. I want to set the visibility of the button according to the value of an integer property of a class. This requires a data binding and a converter.
The XAML code for the button is as follows:
<Window.Resources>
<local:Button1VisibilityConverter x:Key="Button1VisibilityConverter"/>
<local:ModeValues x:Key="ModeHolder"/>
</Window.Resources>
<Grid>
<StackPanel HorizontalAlignment="Left" Height="150" Margin="92,90,0,0" VerticalAlignment="Top" Width="301">
<Button Content="1" Height="58" Background="#FFA20000" Foreground="White" Visibility="{Binding Source={StaticResource ModeHolder}, Path=State, Converter=Button1VisibilityConverter}"/>
<Button Content="2" Height="58" Background="#FF16A200" Foreground="White"/>
<Button Content="3" Height="58" Background="#FF4200A2" Foreground="White"/>
</StackPanel>
</Grid>
My converter is as follows:
class Button1VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targettype, object parameter, System.Globalization.CultureInfo culture)
{
int mode = (int)value;
if (mode == ModeValues.Red)
return System.Windows.Visibility.Visible;
else
return System.Windows.Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
The class that has the property I want to control the visibility is as follows:
public class ModeValues : IObservable<int>
{
private int _state = -1;
public static int Red
{
get
{
return 0;
}
}
public static int Green
{
get
{
return 1;
}
}
public static int Purple
{
get
{
return 2;
}
}
public int State
{
get
{
return this._state;
}
set
{
this.State = value;
}
}
}
I have no idea why it isn't working. I thought I had to bind the visibility to the property of the instance of the ModeHolder, make the ModeHolder observable, and convert the int to a visibility. What am I missing?
Converter=Button1VisibilityConverter
should be:
Converter={StaticResource Button1VisibilityConverter}
Trying to get radiobuttons binding working but getting a run time error with the code below. Want the radio buttons to act such that only one can be selected at a time, and that they bind correctly in a 2 way fashion. Error text is.
"The invocation of the constructor on
type 'testapp1.MainWindow' that
matches the specified binding
constraints threw an exception"
<Window x:Class="testapp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:testapp1" Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<l:EnumBooleanConverter x:Key="enumBooleanConverter" />
</Grid.Resources>
<StackPanel >
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=FirstSelection}">first selection</RadioButton>
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=TheOtherSelection}">the other selection</RadioButton>
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=YetAnotherOne}">yet another one</RadioButton>
<Label Content="{Binding Path=VeryLovelyEnum}" Height="28" Name="label1" />
</StackPanel>
</Grid>
</Window>
And code:
namespace testapp1
{
public partial class MainWindow : Window
{
public TestModel _model;
public MainWindow()
{
InitializeComponent();
InitializeComponent();
_model = new TestModel();
this.DataContext = _model;
}
}
public enum MyLovelyEnum
{
FirstSelection,
TheOtherSelection,
YetAnotherOne
};
public class TestModel : DependencyObject
{
public MyLovelyEnum VeryLovelyEnum
{
get { return (MyLovelyEnum)GetValue(VeryLovelyEnumProperty); }
set { SetValue(VeryLovelyEnumProperty, value); }
}
public static readonly DependencyProperty VeryLovelyEnumProperty =
DependencyProperty.Register("VeryLovelyEnum", typeof(MyLovelyEnum), typeof(TestModel), new UIPropertyMetadata(0));
// from http://stackoverflow.com/questions/397556/wpf-how-to-bind-radiobuttons-to-an-enum
public class EnumBooleanConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string parameterString = parameter as string;
if (parameterString == null)
return DependencyProperty.UnsetValue;
if (Enum.IsDefined(value.GetType(), value) == false)
return DependencyProperty.UnsetValue;
object parameterValue = Enum.Parse(value.GetType(), parameterString);
return parameterValue.Equals(value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string parameterString = parameter as string;
if (parameterString == null)
return DependencyProperty.UnsetValue;
return Enum.Parse(targetType, parameterString);
}
#endregion
}
}
The button & label is there from a previous test I did - I just left in...
The following declaration uses an integer instead of a default enum-value. Could be your instantiation problem...
public static readonly DependencyProperty VeryLovelyEnumProperty =
DependencyProperty.Register("VeryLovelyEnum", typeof(MyLovelyEnum), typeof(TestModel), new UIPropertyMetadata(0));
Try something like...
public static readonly DependencyProperty VeryLovelyEnumProperty =
DependencyProperty.Register("VeryLovelyEnum", typeof(MyLovelyEnum), typeof(TestModel), new UIPropertyMetadata(MyLovelyEnum.FirstSelection));