I'm trying to bind a property of a resource object to a control (a combo...) - the thing seems to work in the designer, but it is not working at runtime.
Just using a simple page with only a button and a combo - resource section:
<UserControl.Resources>
<LinearGradientBrush x:Key="myBrush" EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Offset="0" Color="{Binding ElementName=w_comboColor, Path=SelectedItem.Content}" />
<GradientStop Offset="1" Color="White" />
</LinearGradientBrush>
</UserControl.Resources>
and the widgets section:
<Button Name="w_button" Grid.Row="0" Width="200" Content="Button" Height="60" HorizontalAlignment="Center"
Margin="2" VerticalAlignment="Center" Background="{Binding Source={StaticResource myBrush}}">
</Button>
<ComboBox Grid.Row="1" Height="24" HorizontalAlignment="Stretch" Margin="2"
Name="w_comboColor" VerticalAlignment="Center" SelectedIndex="1" >
<ComboBox.Items>
<ComboBoxItem Content="Red" />
<ComboBoxItem Content="Blue" />
<ComboBoxItem Content="Green" />
</ComboBox.Items>
</ComboBox>
changing the value of the SelectedIndex property of the combo, in the designer, makes the button background to change its background color (as expected).
If I run the sample, nothing works any more :-\
I tried to force the DataContext of the UserControl and other stuff - nothing happens: at runtime the binding is broken.
Any ideas?
You need to add a ValueConverter to your GradientStop.Color binding to convert from a string to a Color. Your current binding is trying to assign a string to a Color property. I would imagine the designer will do the type conversion for you, just like it would be done in XAML, but that won't happen at runtime. You will need a converter something like this:
public class ColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value == "Red")
return Colors.Red;
else if(value == "Blue")
return Colors.Blue;
else if(value == "Green")
return Colors.Green;
}
}
This definitely is not a complete converter, but it should point you in the right direction.
Bindings on resource objects are ignored when these objects inherit directly from DependencyObject (like GradientStop does). Bindings work on resource objects that inherit from FrameworkElement.
Related
Starting with a Grouped Items Page template, I want to be able to perform tasks on the grid items when they are clicked. Namely, I want to change the background image, and add/remove the underlying object to a list of selected items.
Here's my DataTemplate:
<GridView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="LightGray" BorderThickness="2" Margin="0,0,20,20">
<Grid HorizontalAlignment="Left" Width="390" Height="190">
<Grid.Background>
<ImageBrush ImageSource="/Assets/unselected.png" Stretch="None"/>
</Grid.Background>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Image VerticalAlignment="Top" Stretch="None" Source="{Binding ImageUrl}" Margin="10,10,0,0"/>
<StackPanel MaxWidth="270">
<TextBlock Text="{Binding Summary}"/>
<TextBlock Text="{Binding Brand}" />
<TextBlock Text="{Binding Detail}" TextWrapping="Wrap" />
</StackPanel>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</GridView.ItemTemplate>
OnTap, I want to togle the ImageSource value of the Grid.Background from unselected.png to selected.png. This I believe I can do using VisualStates and Storyboards, but I've been unable to get this to work in the past (I'll spare you the chaos of my attempts in xaml).
Needless to say, I've tried following the steps detailed here using Blend, but the Grid.Background property doesn't seems to be state specific. If I try changing the background brush in the Pressed or Selected states, it also changes for the Normal state.
Since I want to grab the data context of the selected item and add/remove it from a list, should I just be handling all this together in an OnTap event handler? I would prefer to keep these concerns separated, but I'll do what I need to...
thanks!
One clean way to do this would be engage the selection method (Tap) in such a way that it only opperates on its items, and the items themselves have properties which Implement the INotifyPropertyChanged interface
Your View Model would have a collection of your custom objects that have properties that can notify the ui
public class MyObject : INotifyPropertyChanged
{
private string _summary;
public string summary
{
get {return _summary}
set
{
_summary = value;
OnPropertyChanged()
}
}
//Other Properties: brand || detail
private ImageSource _backgroundImage;
public ImageSource backgroundImage
{
get {return _backgroundImage}
set
{
_backgroundImage = value;
OnPropertyChanged()
}
}
private bool _IsSelected;
public bool IsSelected
{
get {return _IsSelected;}
set
{
_IsSelected = value;
OnPropertyChanged()
}
}
}
Then although your code behind can be used to change the value of IsSelected, or Background image ... if you choose to go with IsSelected, you can still separate your concerns by not directly setting the resource of the background image in code behind. The Codebehind will only iterate over the items to toggle the IsSelected property, and you can use xaml to define the image that the background should use by creating a custom converter.
public class MyCustomControlOrPage.cs : UserControl //Or ApplicationPage
{
//.......code
protected void HandleTap(object sender, GestureEventArgs e)
{
foreach(var item in ((Listbox)sender).ItemsSource)
{
((MyObject)item.IsSelected = (MyObject)item.Name == (e.NewItems[0] as MyObject).Name? true: false;
}
}
}
then the converter
public class BackgroundConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ImageSource source = value == true ? new BitmapImage(uriForSelected) : new BitmapImage(uriForunselected);
return source;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
BitmapImage thisValue = value as BitmapImage;
return thisValue;
}
}
and FINALLY the XAML where the grid background binds to the IsSelected property and allows the converter to transform the bool to an ImageSource of type BitmapImage:
//add xmlns:Converters=clr-namesapce:Yournamespace.UpTo.TheNamespaceBackgroundConverterIsIn" to the page or control definition
<UserControl.Resources>
<Converters:BackgroundConverter x:key="BgSourceConverter" />
</UserControl.Resources>
<GridView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="LightGray" BorderThickness="2" Margin="0,0,20,20">
<Grid HorizontalAlignment="Left" Width="390" Height="190">
<Grid.Background>
<ImageBrush ImageSource="{Binding Path=IsSelected, Mode=TwoWay, Converter={Binding Source={StaticResource BGSourceConverter}}}" Stretch="None"/>
</Grid.Background>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Image VerticalAlignment="Top" Stretch="None" Source="{Binding ImageUrl}" Margin="10,10,0,0"/>
<StackPanel MaxWidth="270">
<TextBlock Text="{Binding Summary}"/>
<TextBlock Text="{Binding Brand}" />
<TextBlock Text="{Binding Detail}" TextWrapping="Wrap" />
</StackPanel>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</GridView.ItemTemplate>
I am beginning WPF, and I am having a bit of a hard time implementing data binding.
Specifically, I have created a simple user control which holds a Label and a Button.
For this user control, I have created a ViewModel which holds just two properties, string "Text" and SimpleEnum "Status".
The point of the control is to display a status of something, like "Connected" yes/no, etc. The background color of the button indicates the status.
My XAML looks something like this
<Control.DataContext>
<vm:OnOffStatusViewModel />
</Control.DataContext>
<Label x:Name="label1" Height="Auto" HorizontalAlignment="Left" Content="{Binding Text}" Width="280" />
<Button Style="{StaticResource GlassButton}" Height="14" Width="14" Background="{Binding Status}" Grid.Column="1" />
with xmlns:vm="clr-namespace:Controls"
The code-behind has a property ViewModel exposing the view model, implementing INotifyPropertyChanged, and initializes as _viewModel = (OnOffStatusViewModel) DataContext;
Now, in my view that is using this control, I have managed to set the Text to something, as I in my implementing view code-behind have onOffStatus1.ViewModel.Text = ..., however, the status is set by enum, and is as such not really bindable to the background property of the button.
My questions related to this:
Is the way I have done the control correct? If not, what is the proper way of implementing data binding in user controls?
How can I have my enum status update the background property of the button using binding?
How can I have my enum status update the background property of the button using binding?
It's recommended to use a value converter for this task, returning a brush for every possible value of the enumeration. This way, your view model does not need to know anything about colors or brushes, and you can use the converter wherever you would like to visualize the status.
XAML
<UserControl x:Class="WpfApplication1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1">
<UserControl.Resources>
<local:StatusColorConverter x:Key="StatusColorConverter" />
</UserControl.Resources>
<Button Background="{Binding Status, Converter={StaticResource StatusColorConverter}" />
</UserControl>
Converter
public enum Status
{
Connected
}
public class StatusColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
switch ((Status)value)
{
case Status.Connected: return new SolidColorBrush(Colors.Green);
}
return new SolidColorBrush(Colors.Black);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Is the way I have done the control correct? If not, what is the proper
way of implementing data binding in user controls?
Your implementation seems fine to me. You might want to eliminate the coupling between the view model and the view (which currently holds a reference to the view model) via dependency injection. But this depends on your use-cases and the architecture you want to use.
I would take a slightly different approach than the other answers here, I like to put the code and logic into my view models directly, so here's how I would do it:
<Control.DataContext>
<vm:OnOffStatusViewModel />
</Control.DataContext>
<Label x:Name="label1" Height="Auto" HorizontalAlignment="Left" Content="{Binding Text}" Width="280" />
<Button Style="{StaticResource GlassButton}" Height="14" Width="14" Background="{Binding ButtonBg}" Grid.Column="1" />
In the VM:
public MyStatus Status
{
get { return _status; }
set
{
_status = value;
OnPropertyChanged("Status");
ButtonBg = Colors.Red;
}
}
public Color ButtonBg
{
get { ... }
set { ... }
}
Since your button background is bound to a property on your view model, then you have the freedom to change that in reaction to whatever is going on in your view model without needing to move logic or code out to converters and templates.
Personally, I have NOT been able to use custom UserControls with MVVM. Either my mind hasn't wrapped around how to use them together or they just don't mix. I use DataTemplates for everything that's not a Window.
Keeping it concise...
OnOffStatusVM : INPC
string Status
Color Color (or Brush)
(set Color when enum value updates)
(OnOffStatus DataTemplate)
<DataTemplate DataType="{x:Type ViewModel:OnOffStatusVM}" x:Shared="False" x:Key="rezOnOffStatus">
<Grid>
<Label Height="Auto" HorizontalAlignment="Left" Content="{Binding Status}" Width="280" />
<Button Style="{StaticResource GlassButton}" Height="14" Width="14" Background="{Binding Color}" Grid.Column="1" />
</Grid>
</DataTemplate>
Usage if DataContext derives from OnOffStatusVM
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource rezOnOffStatus}" />
Usage if DataContext has a OnOffStatusVM OnOffStatus property
<ContentPresenter Content="{Binding OnOffStatus}" ContentTemplate="{StaticResource rezOnOffStatus}" />
Clarification provided if needed..
I have a ListBox filled with paths of different images. How will I alter the ItemTemplate so that the images will be shown instead of paths(string).
Here is the code:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Image Height="50" Width="50" Source="{Binding Path=Content}" Stretch="Fill"></Image>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0083A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0102A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0103A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0104A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0105A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0106A.jpg</ListBoxItem>
</ListBox>
You could make an IValueConverter that converts a string to a ImageSource.
Something like:
public class ImagePathConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new BitmapImage(new Uri(value as string));
}
public object ConvertBack(xxx) { throw new NotSupportedException(); }
}
And then create a value converter resource and use that in your binding.
a resource could be defined like:
<UserControl.Resources>
<myNameSpaceAlias:ImagePathConverter x:Key="ImagePathConverter"/>
...
and then bind with:
{Binding Path=Content, Converter={StaticResource ImagePathConverter}}
The ItemTemplate of a ListBox is copied to the ContentTemplate of a ListBoxItem during UI generation. However, when adding the ListBoxItems directly, ItemTemplate is ignored for items already of the ItemsControls container type (ListBoxItem for ListBox, ListViewItem for ListView etc.). So in this case, you'll have to use the ContentTemplate of the ItemContainerStyle directly instead.
Also, change Source="{Binding Content}" to Source="{Binding}"
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Height="50" Width="50" Source="{Binding}" Stretch="Fill"></Image>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0083A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0102A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0103A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0104A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0105A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0106A.jpg</ListBoxItem>
</ListBox>
You have to use a value converter in the binding and pass a bitmap image
I have a list of expanders which I want to control its expanded state(IsExpanded) with global toggle button which should toggle between expanded/collapsed state.
The solution which I have got so far does that by binding the expander's IsExpanded state to the togglebutton's IsChecked state. This works as long as I dont manually dont toggle the expanders. Once I do that those particular expanders dont respect the binding (toggle button's IsChecked state).
Any idea why? and is there a clean solution in XAML for this?
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<StackPanel>
<ToggleButton Name="ExpandAll">Toggle</ToggleButton>
<Expander IsExpanded="{Binding ElementName=ExpandAll,Path=IsChecked, Mode=OneWay}">
Hai
</Expander>
<Expander IsExpanded="{Binding ElementName=ExpandAll,Path=IsChecked, Mode=OneWay}">
Hello
</Expander>
<Expander IsExpanded="{Binding ElementName=ExpandAll,Path=IsChecked, Mode=OneWay}">
Weird
</Expander>
</StackPanel>
</Grid>
</Page>
I know that this post is very old. Just posting this for anyone else who comes across. The below code worked for me.
<Expander IsExpanded="{Binding ElementName=ExpandAll, Path=IsChecked, UpdateSourceTrigger=Explicit}">
</Expander>
This works when the expanders are generated dynamically, for example inside a DataGrid.RowDetailsTemplate.
I don't think you can achieve this entirely in XAML, but the following allows you to do it with an IValueConverter:
<StackPanel>
<StackPanel.Resources>
<local:Converter x:Key="Converter" />
</StackPanel.Resources>
<ToggleButton Name="ExpandAll">
<ToggleButton.IsChecked>
<MultiBinding Mode="OneWayToSource" Converter="{StaticResource Converter}">
<Binding ElementName="Expander1" Path="IsExpanded" />
<Binding ElementName="Expander2" Path="IsExpanded" />
<Binding ElementName="Expander3" Path="IsExpanded" />
</MultiBinding>
</ToggleButton.IsChecked>
Toggle</ToggleButton>
<Expander Name="Expander1">
Hai
</Expander>
<Expander Name="Expander2">
Hello
</Expander>
<Expander Name="Expander3">
Weird
</Expander>
</StackPanel>
And your Converter is as below:
public class Converter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//we're using OneWayToSource, so this will never be used.
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
// we want to convert the single 'IsChecked' value from the ToggleButton into
// 3 'IsExpanded' values
var allValues = new object[targetTypes.Length];
for (int i = 0; i < allValues.Length; i++)
{
allValues[i] = value;
}
return allValues;
}
}
This works by setting up a OneWayToSource binding between the IsChecked property of the ToggleButton (i.e. the binding will be set against the source when the target value changes), and uses an IMultiValueConverter to translate the single value into one for each of the Expanders.
I have a question regarding how to best accomplish something in WPF MVVM. I have in my ViewModel a series of integers. For the sake of example, lets call them:
public int Yellow
{
get;set;
}
public int Red
{
get;set;
}
public int Green
{
get;set;
}
I also have some small images that are very simple: A Red circle, a Yellow circle, and a Green circle. The idea is to have an area on the view with a number of these images, based on the above properties. So if this instance of the view model has 3 Yellow, 2 Red, and 1 Green, I want 6 images in my ListBox, 3 of the yellow circle, 2 of the red, and 1 of the green. Right now, I have it working, but using some very clumsy code where I build the image list in the ViewModel using an ugly for-loop. Is there some more elegant way to accomplish this task in WPF? Ideally, I wouldn't want to have to reference the image in the ViewModel at all...
You could use an ImageBrush to tile a rectangle with an image, and bind the width of the rectangle to the number of copies of the image you want. Something like this:
<StackPanel Orientation="Horizontal">
<StackPanel.LayoutTransform>
<ScaleTransform ScaleX="20" ScaleY="20"/>
</StackPanel.LayoutTransform>
<Rectangle Width="{Binding Yellow}" Height="1">
<Rectangle.Fill>
<ImageBrush
ImageSource="Yellow.png"
Viewport="0,0,1,1"
ViewportUnits="Absolute"
TileMode="Tile"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle Width="{Binding Red}" Height="1">
<Rectangle.Fill>
<ImageBrush
ImageSource="Red.png"
Viewport="0,0,1,1"
ViewportUnits="Absolute"
TileMode="Tile"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle Width="{Binding Green}" Height="1">
<Rectangle.Fill>
<ImageBrush
ImageSource="Green.png"
Viewport="0,0,1,1"
ViewportUnits="Absolute"
TileMode="Tile"/>
</Rectangle.Fill>
</Rectangle>
</StackPanel>
Update: As Ray pointed out in his comment, if you are just trying to draw circles then you will get better zoom behavior by using a DrawingBrush than by using an Image:
<StackPanel Orientation="Horizontal">
<StackPanel.LayoutTransform>
<ScaleTransform ScaleX="20" ScaleY="20"/>
</StackPanel.LayoutTransform>
<StackPanel.Resources>
<EllipseGeometry x:Key="Circle" RadiusX="1" RadiusY="1"/>
</StackPanel.Resources>
<Rectangle Width="{Binding Yellow}" Height="1">
<Rectangle.Fill>
<DrawingBrush ViewportUnits="Absolute" TileMode="Tile">
<DrawingBrush.Drawing>
<GeometryDrawing
Brush="Yellow"
Geometry="{StaticResource Circle}"/>
</DrawingBrush.Drawing>
</DrawingBrush>
</Rectangle.Fill>
</Rectangle>
<!-- etc. -->
A possibility would be to use a ValueConverter. It is very flexible, decoupled and helps to let the xaml simple. Here the code for such a value-converter:
public class ImageCountValueConverter : IValueConverter{
public string ImagePath {
get;
set;
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if(null == value){
return Enumerable.Empty<string>();
} else if (value is int) {
List<string> list = new List<string>();
int v = (int)value;
for (int i = 0; i < v; i++) {
if (parameter is string) {
list.Add((string)parameter);
} else {
list.Add(ImagePath);
}
}
return list;
} else {
Type t = value.GetType();
throw new NotSupportedException("The \"" + t.Name+ "\" type is not supported");
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
}
The markup would look like this:
<StackPanel>
<ItemsControl ItemsSource="{Binding Yellow,Converter={StaticResource ImageCount_ValueConverter},ConverterParameter=/image/yellow.png}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Stretch="None"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding Red,Converter={StaticResource ImageCount_ValueConverter},ConverterParameter=/image/red.png}" >
...
The declaration would look something like:
<Window.Resources>
<local:ImageCountValueConverter x:Key="ImageCount_ValueConverter" ImagePath="/image/sampleImage.png"/>
</Window.Resources>
Options
Depending on your requirements you can also extend it or change it to work with ImageSource instead of strings or even provide a List<Brush> as output and then use a shape in your DataTemplate where the Brush is set through the Binding.