I have 2 TextBoxes in a Usercontrol (InfoControl) bound to a Point property in a VM implementing INotifyPropertyChanged.
I have another UserControl (DesignerControl) which contains a draggable rectangle.
The Rectangle has its Canvas.Left and Canvas.Bottom bound to a ConvPoint property of the same VM.
ConvPoint Property (0 => ActualWidth) is a converted version of the Point Property (0 => 1)
when i drag my rectangle, the ConvPoint VM Property and the values in the textboxes are instantly updated, but when i update my textboxes with new values, the VM Point Property is instantly updated but the Rectangle is positioned only when i drag the rectangle again and not instantly.
A bit of code to explain:
First, my ViewModel's Position Property
public class MyVM : ViewModelBase
{
private DependencyPoint position;
public DependencyPoint Position
{
get { return this.position; }
set
{
this.position = value;
RaisePropertyChanged("Position");
}
}
public DependencyPoint ConvPosition
{
get { return new Point(this.Position.X * MainVM.ActualWidth, this.Position.Y * MainVM.AcutalHeight);}
set
{
Point p = new Point(value.X/MainVM.ActualWidth,value.Y/MainVM.ActualHeight);
this.position = p;
RaisePropertyChanged("ConvPosition");
}
}
}
Edit:
I'm using for this a class DependencyPoint to have a notification on the X and Y properties:
public class DependencyPoint : DependencyObject
{
public enum PointOrder
{
isStartPoint,
isEndPoint,
isPoint1,
isPoint2
}
public DependencyPoint()
{
}
public DependencyPoint(Double x, Double y, PointOrder po)
{
this.X = x;
this.Y = y;
this.Order = po;
}
public DependencyPoint(DependencyPoint point)
{
this.X = point.X;
this.Y = point.Y;
this.Order = point.Order;
}
public DependencyPoint(Point point, PointOrder po)
{
this.X = point.X;
this.Y = point.Y;
this.Order = po;
}
public Point ToPoint()
{
return new Point(this.X, this.Y);
}
public PointOrder Order
{
get { return (PointOrder)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
// Using a DependencyProperty as the backing store for Order. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OrderProperty =
DependencyProperty.Register("Order", typeof(PointOrder), typeof(DependencyPoint), new UIPropertyMetadata(null));
public Double X
{
get { return (Double)GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
// Using a DependencyProperty as the backing store for X. This enables animation, styling, binding, etc...
public static readonly DependencyProperty XProperty =
DependencyProperty.Register("X", typeof(Double), typeof(DependencyPoint), new UIPropertyMetadata((double)0.0));
public Double Y
{
get { return (Double)GetValue(YProperty); }
set { SetValue(YProperty, value); }
}
// Using a DependencyProperty as the backing store for Y. This enables animation, styling, binding, etc...
public static readonly DependencyProperty YProperty =
DependencyProperty.Register("Y", typeof(Double), typeof(DependencyPoint), new UIPropertyMetadata((double)0.0));
}
then in my InfoControl:
<Grid Grid.Row="0" DataContext="{Binding Position}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Margin="3,3,0,1" Grid.Row="0" LastChildFill="True">
<TextBlock Foreground="White" Padding="0,3,0,0" Margin="2,0,0,0" DockPanel.Dock="Left" Text="StartPoint.X :"/>
<TextBox FontWeight="DemiBold" Foreground="Black" Background="#efefef" Width="Auto" Margin="7,0,0,0" Text="{Binding X, Converter={StaticResource StDConverter}, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"/>
</DockPanel>
<DockPanel Margin="3,3,0,1" Grid.Row="1" LastChildFill="True">
<TextBlock Foreground="White" Padding="0,3,0,0" Margin="2,0,0,0" DockPanel.Dock="Left" Text="StartPoint.Y :"/>
<TextBox FontWeight="DemiBold" Foreground="Black" Background="#efefef" Width="Auto" Margin="7,0,0,0" Text="{Binding Y, Converter={StaticResource StDConverter}, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"/>
</DockPanel>
</Grid>
And in the DesignerControl:
<UserControl>
<Canvas>
<Rectangle x:Name="Point" Width="10" Height="10" Canvas.Bottom="{Binding ConvPosition.Y, Mode=TwoWay}" Canvas.Left="{Binding ConvPosition.X, Mode=TwoWay}" />
</Canvas>
</UserControl>
I have access to the actualWidth and my Rectangle is well positioned in the Canvas.
I know my properties in the VM or a bit dirty but i don't know an other way to do it properly and manage the conversion too.
any ideas?
Because ConvPosition depends from Position you should raise a NotificationChanged for ConvPosition in Position setter.
public Point Position
{
get { return this.position; }
set
{
this.position = value;
RaisePropertyChanged("Position");
RaisePropertyChanged("ConvPosition");
}
}
ConvPosition you can change to this
public Point ConvPosition
{
get { return new Point(this.Position.X * MainVM.ActualWidth, this.Position.Y * MainVM.AcutalHeight); }
set
{
this.Position = new Point(value.X/MainVM.ActualWidth, value.Y/MainVM.ActualHeight);
}
}
EDIT
I think two additional properties in MyVM would be the simpliest solution.
public double X
{
get { return Position.X; }
set
{
Position = new Point(value, Position.Y);
RaisePropertyChanged("X");
}
}
public double Y
{
get { return Position.Y; }
set
{
Position = new Point(Position.X, value);
RaisePropertyChanged("Y");
}
}
Only change in Grid:
<Grid DataContext="{Binding}">
Related
My scenario is that there are two controls. One in which you set up minutes and second in which you specify seconds.
Both of them should be bound to single property in view model. This property is of type string. This string is in format [hh:mm:ss]. So changing value in "minutes" control should change 'mm' portion of the string and changing the value in "seconds" control should change the 'ss' portion of the string.
Thanks in advance
Here is a 3-property ViewModel working solution if you are using TimeSpan and its range is between 0 and 59h 59s. I have not fully tested and conditions/validation will change based on requirements. I used TimeSpan.TotalSeconds because that's the resolution we needed; meaning, when setting the TimeSpan to a new value, we would just set the total number of seconds through the public property. An alternative could be to have 2 TimeSpan properties in your ViewModel, then when setting the public property, you could call _item.TotalSeconds = VMMinutes.TotalSeconds + VMSeconds.TotalSeconds.TotalSeconds. Basically you have many design options here.
MainWindow.xaml:
<Grid>
<StackPanel>
<Border Height="60" BorderBrush="Black" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Label Content="Minutes"/>
<TextBox Text="{Binding Minutes}" />
<Label Content="Seconds"/>
<TextBox Text="{Binding Seconds}" />
</StackPanel>
</Border>
<Border Height="60" BorderBrush="Black" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Label Content="Total Seconds"/>
<TextBox Text="{Binding TotalSeconds}" />
</StackPanel>
</Border>
</StackPanel>
</Grid>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ItemViewModel(new Item(new TimeSpan(0, 3, 59)));
}
}
ItemViewModel.cs:
public class ItemViewModel : INotifyPropertyChanged
{
private readonly Item _item;
public event PropertyChangedEventHandler PropertyChanged;
public ItemViewModel(Item item)
{
_item = item;
}
public string TotalSeconds
{
get
{
return _item.TotalSeconds.ToString();
}
set
{
double newTotSecs;
if(!string.IsNullOrEmpty(value))
{
if(double.TryParse(value, out newTotSecs))
{
_item.TotalSeconds = newTotSecs;
NotifyPropertyChanged();
NotifyPropertyChanged("Minutes");
NotifyPropertyChanged("Seconds");
}
}
}
}
public string Seconds
{
get
{
return (_item.TotalSeconds % 60).ToString();
}
set
{
int newVal;
if(!string.IsNullOrEmpty(value))
{
if(int.TryParse(value, out newVal))
{
if(newVal >= 0 && newVal <= 59)
{
int totMinSec;
if(int.TryParse(Minutes, out totMinSec))
{
_item.TotalSeconds = (totMinSec * 60) + newVal;
NotifyPropertyChanged();
NotifyPropertyChanged("TotalSeconds");
}
}
}
}
}
}
public string Minutes
{
get
{
return ((int)(_item.TotalSeconds / 60)).ToString();
}
set
{
int newVal;
if(!string.IsNullOrEmpty(value))
{
if(int.TryParse(value, out newVal))
{
if(newVal >= 0 && newVal <= 59)
{
int totSec;
if(int.TryParse(Seconds, out totSec))
{
_item.TotalSeconds = totSec + (newVal * 60);
NotifyPropertyChanged();
NotifyPropertyChanged("TotalSeconds");
}
}
}
}
}
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Item.cs:
public class Item
{
private TimeSpan _time;
public double TotalSeconds
{
get
{
return _time.TotalSeconds;
}
set
{
if(value >= 0)
{
_time = new TimeSpan(0, 0, (int)value);
}
}
}
public Item(TimeSpan time)
{
_time = time;
}
}
Note: Your other option is to use a Converter, which I haven't provided a solution for. I think it could end up being cleaner in the long run since all you really need to pass to back and forth is the converter is total number of seconds.
I would use NETScape's approach above, but encapsulate it in a user control. The user control XAML would be something like:
<UserControl>
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Minutes" Grid.Row="0" Grid.Column="0"/>
<TextBox Text="{Binding InternalMinutes}" Grid.Row="0" Grid.Column="1"/>
<TextBlock Text="Seconds" Grid.Row="1" Grid.Column="0"/>
<TextBox Text="{Binding InternalSeconds}" Grid.Row="1" Grid.Column="1"/>
</Grid>
</UserControl>
Then in the code-behind, you would have a Dependency Property for the actual DateTime object, and properties to bind against (you could use a view model for this, or just go off of TextChanged. When its all View logic, its ok!).
An example property would be:
public int InternalSeconds
{
get { return ExternalTime.Seconds; }
set
{
ExternalTime.Seconds = value;
NotifyPropertyChanged();
}
}
Again, there are multiple approaches here, you could use a converter in order to use an intermediate object. ExternalTime is the DP here, make sure to handle its Changed event if you expect the value to change outside of this control.
I'm using PixelShader effects for an image.
Image effects for adjusting Contrast, Brightness, CMY & RGB using SLIDERS
Blend mode Image effects which are predefined & loaded in a combo box and users can select their own choice.
This is the Image element and the image effect for adjusting Contrast, Brightness, CMY & RGB are applied in this way.
<Viewbox x:Name="cImage" Stretch="Uniform" Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" Margin="0" >
<Image x:Name="ViewedPhoto" Source="IMG_0071.jpg"
Stretch="None" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="0,5,0,95" >
<Image.Effect>
<l:BrightContrastEffect
Brightness="{Binding Value, ElementName=bVal}"
Contrast="{Binding Value, ElementName=cVal}"
Red="{Binding Value, ElementName=rVal}"
Green="{Binding Value, ElementName=gVal}"
Blue="{Binding Value, ElementName=blVal}"
/>
</Image.Effect>
</Image>
</Viewbox>
Slider for brightness:
<Slider Maximum="1" Minimum="-1" x:Name="bVal" TickFrequency="1" TickPlacement="BottomRight" />
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Value, ElementName=bVal, UpdateSourceTrigger=PropertyChanged}" TextAlignment="Right" Width="30" Height="10" Margin="0" RenderTransformOrigin="-1.167,0.423" Visibility="Hidden"/>
</StackPanel>
From the above when i move the sliders for adjusting Contrast, Brightness, CMY & RGB, it's working.
I apply predefined Blend mode image effects using comboBox.
<ComboBox x:Name="cColorEffects" SelectionChanged="cColorEffects_SelectionChanged" />
To show the different effects i call ViewedPhoto.Effect = null; at first & then apply the selected effect.
if (cColorEffects.SelectedIndex == 0)
{
ViewedPhoto.Effect = null;
ViewedPhoto.Effect = new AverageEffect();
}
if (cColorEffects.SelectedIndex == 1)
{
ViewedPhoto.Effect = null;
ViewedPhoto.Effect = new ColorBurnEffect();
}
Problems:
I'm calling another image effects through comboBox so the image effects for adjusting Contrast, Brightness, CMY & RGB are not working.
I'm using ViewedPhoto.Effect = null; the image effects for adjusting Contrast, Brightness, CMY & RGB are not working.
I want to make the sliders working for adjusting Contrast, Brightness, CMY & RGB and apply the blend mode image effects simultaneously. How can i fix this? Or Give me an idea to fix this?
EDIT:
I have been thinking I could do make the slider binding pro-grammatically and the image effect. Does it make sense? if it's good; How can i apply this?
<Image x:Name="ViewedPhoto" >
<Image.Effect>
<l:BrightContrastEffect
Brightness="{Binding Value, ElementName=bVal}"
Contrast="{Binding Value, ElementName=cVal}"
Red="{Binding Value, ElementName=rVal}"
Green="{Binding Value, ElementName=gVal}"
Blue="{Binding Value, ElementName=blVal}"
/>
</Image.Effect>
</Image>
<Slider Maximum="1" Minimum="-1" x:Name="bVal" TickFrequency="1" TickPlacement="BottomRight" />
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Value, ElementName=bVal, UpdateSourceTrigger=PropertyChanged}" Visibility="Hidden"/>
<Button x:Name="bReset" Content="R" Height="10" Width="30" Margin="35,0,0,0" Click="bReset_Click"/>
</StackPanel>
<Slider Maximum="1" Minimum="-1" x:Name="cVal" TickFrequency="1" TickPlacement="BottomRight" />
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Value, ElementName=cVal, UpdateSourceTrigger=PropertyChanged}" Visibility="Hidden" />
<Button x:Name="cReset" Content="R" Height="10" Width="30" Margin="35,0,0,0" Click="cReset_Click"/>
</StackPanel>
Additional info with codes:
public class BlendModeEffect : ShaderEffect
{
public BlendModeEffect()
{
UpdateShaderValue(InputProperty);
UpdateShaderValue(TextureProperty);
}
public Brush Input
{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}
public static readonly DependencyProperty InputProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty
(
"Input",
typeof(BlendModeEffect),
0
);
public Brush Texture
{
get { return (Brush)GetValue(TextureProperty); }
set { SetValue(TextureProperty, value); }
}
public static readonly DependencyProperty TextureProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty
(
"Texture",
typeof(BlendModeEffect),
1
);
}
//Contrast, Brightness, CMY & RGB Effect
public class BrightContrastEffect : ShaderEffect
{
private static PixelShader m_shader =
new PixelShader() { UriSource = MakePackUri("bricon.ps") };
public BrightContrastEffect()
{
PixelShader = m_shader;
UpdateShaderValue(InputProperty);
UpdateShaderValue(BrightnessProperty);
UpdateShaderValue(ContrastProperty);
UpdateShaderValue(RedProperty);
UpdateShaderValue(GreenProperty);
UpdateShaderValue(BlueProperty);
}
// MakePackUri is a utility method for computing a pack uri
// for the given resource.
public static Uri MakePackUri(string relativeFile)
{
Assembly a = typeof(BrightContrastEffect).Assembly;
// Extract the short name.
string assemblyShortName = a.ToString().Split(',')[0];
string uriString = "pack://application:,,,/" +
assemblyShortName +
";component/" +
relativeFile;
return new Uri(uriString);
}
public Brush Input
{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}
public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(BrightContrastEffect), 0);
public float Brightness
{
get { return (float)GetValue(BrightnessProperty); }
set { SetValue(BrightnessProperty, value); }
}
public static readonly DependencyProperty BrightnessProperty = DependencyProperty.Register("Brightness", typeof(double), typeof(BrightContrastEffect), new UIPropertyMetadata(0.0, PixelShaderConstantCallback(0)));
public float Contrast
{
get { return (float)GetValue(ContrastProperty); }
set { SetValue(ContrastProperty, value); }
}
public static readonly DependencyProperty ContrastProperty = DependencyProperty.Register("Contrast", typeof(double), typeof(BrightContrastEffect), new UIPropertyMetadata(0.0, PixelShaderConstantCallback(1)));
public float Red
{
get { return (float)GetValue(RedProperty); }
set { SetValue(RedProperty, value); }
}
public static readonly DependencyProperty RedProperty = DependencyProperty.Register("Red", typeof(double), typeof(BrightContrastEffect), new UIPropertyMetadata(0.0, PixelShaderConstantCallback(2)));
public float Green
{
get { return (float)GetValue(GreenProperty); }
set { SetValue(RedProperty, value); }
}
public static readonly DependencyProperty GreenProperty = DependencyProperty.Register("Green", typeof(double), typeof(BrightContrastEffect), new UIPropertyMetadata(0.0, PixelShaderConstantCallback(3)));
public float Blue
{
get { return (float)GetValue(BlueProperty); }
set { SetValue(BlueProperty, value); }
}
public static readonly DependencyProperty BlueProperty = DependencyProperty.Register("Blue", typeof(double), typeof(BrightContrastEffect), new UIPropertyMetadata(0.0, PixelShaderConstantCallback(4)));
//private static PixelShader m_shader = new PixelShader() { UriSource = new Uri(#"pack://application:,,,/CustomPixelRender;component/bricon.ps") };
}
//Average Blend Mode Effect
public class AverageEffect : BlendModeEffect
{
public static Uri MakePackUri(string relativeFile)
{
Assembly a = typeof(ColorBurnEffect).Assembly;
// Extract the short name.
string assemblyShortName = a.ToString().Split(',')[0];
string uriString = "pack://application:,,,/" +
assemblyShortName +
";component/" +
relativeFile;
return new Uri(uriString);
}
static AverageEffect()
{
_pixelShader.UriSource = MakePackUri("AverageEffect.ps");
}
public AverageEffect()
{
this.PixelShader = _pixelShader;
}
private static PixelShader _pixelShader = new PixelShader();
}
//ColorBurn Effect
public class ColorDodgeEffect : BlendModeEffect
{
public static Uri MakePackUri(string relativeFile)
{
Assembly a = typeof(ColorBurnEffect).Assembly;
// Extract the short name.
string assemblyShortName = a.ToString().Split(',')[0];
string uriString = "pack://application:,,,/" +
assemblyShortName +
";component/" +
relativeFile;
return new Uri(uriString);
}
static ColorDodgeEffect()
{
_pixelShader.UriSource = MakePackUri("ColorDodgeEffect.ps");
}
public ColorDodgeEffect()
{
this.PixelShader = _pixelShader;
}
private static PixelShader _pixelShader = new PixelShader();
}
//BlendModeEffect
public class BlendModeEffect : ShaderEffect
{
public BlendModeEffect()
{
UpdateShaderValue(InputProperty);
UpdateShaderValue(TextureProperty);
}
public Brush Input
{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}
public static readonly DependencyProperty InputProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty
(
"Input",
typeof(BlendModeEffect),
0
);
public Brush Texture
{
get { return (Brush)GetValue(TextureProperty); }
set { SetValue(TextureProperty, value); }
}
public static readonly DependencyProperty TextureProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty
(
"Texture",
typeof(BlendModeEffect),
1
);
}
As per the SO chat uses suggestion. I have called BrightContrastEffect as new Effect to the image in the slider changed event Handler. I set the biding values pro-grammatically.
private void cVal_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
cColorEffects.SelectedIndex = 0;
BrightContrastEffect bce = new BrightContrastEffect();
BindingOperations.SetBinding(bce, BrightContrastEffect.ContrastProperty, new Binding("Value") { Source = cVal });
ViewedPhoto.Effect = bce;
}
I have applied the same approached for the other sliders.
I have a custom usercontrol with that code:
public partial class AudioControl : UserControl
{
public AudioControl()
{
InitializeComponent();
Value = 0.1;
DataContext = this;
}
public event RoutedPropertyChangedEventHandler<double> ValueChanged = delegate { };
public int TextWidth
{
get { return (int)GetValue(TextWidthProperty); }
set { SetValue(TextWidthProperty, value); }
}
public int SliderWidth
{
get { return (int)GetValue(SliderWidthProperty); }
set { SetValue(SliderWidthProperty, value); }
}
public string Header
{
get { return (string)GetValue(TextBlock.TextProperty); }
set { SetValue(TextBlock.TextProperty, value); }
}
//public double Value
//{
// get { return (double)GetValue(Slider.ValueProperty); }
// set { SetValue(Slider.ValueProperty, value); }
//}
public double Value
{
get {
return (double)GetValue(ValueProperty);
}
set {
SetValue(ValueProperty, value);
}
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(AudioControl), new UIPropertyMetadata(0));
public static readonly DependencyProperty TextWidthProperty =
DependencyProperty.Register("TextWidth", typeof(int), typeof(AudioControl), new UIPropertyMetadata(0));
public static readonly DependencyProperty SliderWidthProperty =
DependencyProperty.Register("SliderWidth", typeof(int), typeof(AudioControl), new UIPropertyMetadata(0));
private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
ValueChanged(this, e);
}
}
And this XAML:
<UserControl x:Class="Controller.Audio.AudioControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:Audio="clr-namespace:Controller.Audio"
mc:Ignorable="d"
d:DesignHeight="23" d:DesignWidth="500" x:Name="audioControl">
<UserControl.Resources>
<Audio:DoubleToIntConverter x:Key="doubleToInt"/>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding TextWidth}"/>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="{Binding SliderWidth}"/>
</Grid.ColumnDefinitions>
<Label x:Name="txtBlock" Margin="0,0,10,0">
<Binding Path="Header"/>
</Label>
<Label Grid.Column="1" Content="{Binding ElementName=slider, Path=Value, Converter={StaticResource doubleToInt}}"/>
<Slider x:Name="slider" Grid.Column="2" Style="{StaticResource Office2010SilverSliderStyle}"
Value="{Binding Path=Value, ElementName=audioControl}" Minimum="0.0" Maximum="1.0" LargeChange="0.25" TickFrequency="0.01" ValueChanged="Slider_ValueChanged"/>
</Grid>
So if i start it I get an exception that something with the value binding is wrong. I also tried the commented version (the value-Property). There is no exception but if i set the value of that property my slider does not change.
Does anyone has any idea why? I ve never done something like that :(
There's an error in the following dependency property declaration:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(AudioControl), new UIPropertyMetadata(0));
The problem here is that you're defining the dependency property to be of type double, but giving it a default value 0 that is an int. Try changing 0 to 0.0.
I ran your code and encountered an exception. The innermost exception contained the following message:
Default value type does not match type of property 'Value'.
I changed the 0 in the line above to 0.0 and the problem went away.
Is anyone aware of a free or commercial WPF control that would do something like this:
X character per box, and auto-tabbing to the next box as you complete each box? Similar to the way that license keys are entered for Microsoft products.
I don't think it would be particularly hard to do from scratch, but I'd like to avoid reinventing the wheel if a good example of this already exists.
WPF provides all you need except auto-tabbing to the next control. A behavior can provide that functionality and the result looks like this:
<Grid>
<Grid.Resources>
<local:KeyTextCollection x:Key="keys"/>
</Grid.Resources>
<StackPanel>
<ItemsControl ItemsSource="{StaticResource keys}" Focusable="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Background="AliceBlue"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Text}" Margin="5" MaxLength="4" Width="40">
<i:Interaction.Behaviors>
<local:TextBoxBehavior AutoTab="True" SelectOnFocus="True"/>
</i:Interaction.Behaviors>
</TextBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
Here are the KeyText and KeyTextCollection classes (adjust to taste):
class KeyText
{
public string Text { get; set; }
}
class KeyTextCollection : List<KeyText>
{
public KeyTextCollection()
{
for (int i = 0; i < 4; i++) Add(new KeyText { Text = "" });
}
}
And here is the behavior that implements auto-tab and select-on-focus:
public class TextBoxBehavior : Behavior<TextBox>
{
public bool SelectOnFocus
{
get { return (bool)GetValue(SelectOnFocusProperty); }
set { SetValue(SelectOnFocusProperty, value); }
}
public static readonly DependencyProperty SelectOnFocusProperty =
DependencyProperty.Register("SelectOnFocus", typeof(bool), typeof(TextBoxBehavior), new UIPropertyMetadata(false));
public bool AutoTab
{
get { return (bool)GetValue(AutoTabProperty); }
set { SetValue(AutoTabProperty, value); }
}
public static readonly DependencyProperty AutoTabProperty =
DependencyProperty.Register("AutoTab", typeof(bool), typeof(TextBoxBase), new UIPropertyMetadata(false));
protected override void OnAttached()
{
AssociatedObject.PreviewGotKeyboardFocus += (s, e) =>
{
if (SelectOnFocus)
{
Action action = () => AssociatedObject.SelectAll();
AssociatedObject.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
}
};
AssociatedObject.TextChanged += (s, e) =>
{
if (AutoTab)
{
if (AssociatedObject.Text.Length == AssociatedObject.MaxLength &&
AssociatedObject.SelectionStart == AssociatedObject.MaxLength)
{
AssociatedObject.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
};
}
}
If you are not familiar with behaviors, Install the Expression Blend 4 SDK and add this namespaces:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
and add System.Windows.Interactivity to your project.
I have two radio buttons working as radioButton List in UI using MVVM. When the user control is loaded first time, one of the radio button is selected and the related controls are shown in UI... Now when I change the radio button, UI is not getting updated.
Below is the sample XAML:
<Label Grid.Column="0" Grid.Row="3" Content="Exchange Details:" Margin="3" VerticalContentAlignment="Center" Style="{StaticResource NormalLabelStyle}"></Label>
<Grid Grid.Column="1" Grid.Row="3" Width="200">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<RadioButton GroupName="rdoExchange" Content="Basic" IsChecked="{Binding Path=ExchangeDetailsBasic}" Grid.Column="0" VerticalContentAlignment="Center" VerticalAlignment="Center"></RadioButton>
<RadioButton GroupName="rdoExchange" Content="Advanced" IsChecked="{Binding Path=ExchangeDetailsAdvanced}" Grid.Column="2" VerticalContentAlignment="Center" VerticalAlignment="Center"></RadioButton
</Grid>
<Label Grid.Column="3" Grid.Row="0" Content="Number of Mailbox Profiles:" VerticalContentAlignment="Center" Style="{StaticResource NormalLabelStyle}" Visibility="{Binding Path=IsAdvanced}" ></Label>
<telerik:RadNumericUpDown Grid.Column="4" Grid.Row="0" Margin="3" Value="{Binding Path=NumberofMailboxProfiles}" IsInteger="True" Minimum="1" Maximum="4" HorizontalAlignment="Left" Visibility="{Binding Path=IsAdvanced}">< /telerik:RadNumericUpDown>
Below is my ViewModel code:
private enum ExchangeDetails{
Basic,
Advanced
}
private bool isBasicMode = true;
public bool ExchangeDetailsBasic {
get {
return this.isBasicMode;
}
set {
if (value) {
this.applicationSpecificRequirements[ExchangeDetailsKey] = ExchangeDetails.Basic.ToString();
if (!this.isBasicMode) {
this.CheckBasicOrAdvancedSelecteAndDisplayView();
}
}
}
}
public bool ExchangeDetailsAdvanced {
get {
return !this.isBasicMode;
}
set {
if (value) {
this.applicationSpecificRequirements[ExchangeDetailsKey] = ExchangeDetails.Advanced.ToString();
this.CheckBasicOrAdvancedSelecteAndDisplayView();
}
}
}
public Visibility IsAdvanced { get; private set; }
private void CheckBasicOrAdvancedSelecteAndDisplayView() {
this.isBasicMode = this.applicationSpecificRequirements.ContainsKey(ExchangeDetailsKey) ? (this.applicationSpecificRequirements[ExchangeDetailsKey].Equals(ExchangeDetails.Basic.ToString()) ? true : false) : true;
this.IsAdvanced = this.isBasicMode ? Visibility.Collapsed : Visibility.Visible;
}
Radio buttons, groups, and binding don't mix. This is, amazingly, by design.
There are three ways to change the value of a bound control in the UI. One is that the user can do it himself with a mouse click or keypress. The second is that code can change the value of the data source, and binding will update the value in the UI.
The third way is to set the value explicitly in code. If you do this, the binding on the control you've just set is disabled.
This is a little counter-intuitive. You'd expect the new value to get pushed to the data source. The design assumption is that if you wanted the value to get changed in the data source, you'd change it in the data source, and that your code is manipulating the UI because you don't want it to be bound anymore. This gives you a simple way of manually overriding binding - just set the value of the control in code - that doesn't compel you to find the Binding object and manipulate it explicitly. This makes a certain amount of sense. I guess.
But it creates problems with radio buttons. Because grouped radio buttons change each others' values in code. If you have three radio buttons in a group, and one gets checked, the radio button finds the other buttons in the group and unchecks them. You can see this if you look at the code in Reflector.
So what happens is exactly what you're observing: you click on radio buttons and binding gets disabled.
Here's what you do about it - and this actually makes a considerable amount of sense. Don't use groups. You can use radio buttons, but only for their visual style. Disregard their grouping functionality.
Instead, implement the logic that makes the bound boolean properties mutually exclusive in your view model, e.g.:
public bool Option1
{
set
{
_Option1 = value;
if (value)
{
Option2 = false;
Option3 = false;
}
OnPropertyChanged("Option1");
}
}
If you think about it, this logic really shouldn't be in the view anyway. Because it's logic, and that's what the view model is for. So while it's something of a pain, you can console yourself with the thought that architecturally it's the right thing to do.
I guess you are missing the implementation of INotifyPropertyChanged for the view model class. If you have used two way data binding and you are raising the property changed event when the selection changes everything should work fine. #Zamboni has explained it with the code example.
If you implement INotifyPropertyChanged in your view model and you set Binding Mode=TwoWay in your XAML, you can let the binding take care of the rest for you.
Here is sample using some of your code:
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<RadioButton GroupName="rdoExchange" Content="Basic"
IsChecked="{Binding Path=ExchangeDetailsBasic, Mode=TwoWay}"
Grid.Column="0"
VerticalContentAlignment="Center"
VerticalAlignment="Center"/>
<RadioButton GroupName="rdoExchange" Content="Advanced"
IsChecked="{Binding Path=ExchangeDetailsAdvanced, Mode=TwoWay}"
Grid.Column="1"
VerticalContentAlignment="Center"
VerticalAlignment="Center"/>
<Label Grid.Column="0" Grid.Row="1" Grid.RowSpan="2"
Content="Number of Mailbox Profiles:"
VerticalContentAlignment="Center"
Visibility="{Binding Path=IsAdvanced, Mode=TwoWay}" />
</Grid>
Here is the ViewModel:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
}
private bool _isBasicMode = true;
public bool ExchangeDetailsBasic
{
get
{
return this._isBasicMode;
}
set
{
this._isBasicMode = value;
if (value)
{
ExchangeDetailsAdvanced = false;
IsAdvanced = Visibility.Collapsed;
}
this.OnPropertyChanged("ExchangeDetailsBasic");
}
}
private bool _isAdvancedMode = false;
public bool ExchangeDetailsAdvanced
{
get
{
return this._isAdvancedMode;
}
set
{
_isAdvancedMode = value;
if (value)
{
ExchangeDetailsBasic = false;
IsAdvanced = Visibility.Visible;
}
this.OnPropertyChanged("ExchangeDetailsAdvanced");
}
}
private Visibility _isAdvanced = Visibility.Collapsed;
public Visibility IsAdvanced
{
get
{
return _isAdvanced;
}
set
{
_isAdvanced = value;
this.OnPropertyChanged("IsAdvanced");
}
}
}
Here is the base class that implements INotifyPropertyChanged.
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Robert Rossney's answer is great, but I still think that radio buttons should behave like radio buttons and let the VM handle more important logic.
Here is my solution: an attached property that toggles the IsChecked property of all buttons in the same group. Works on my machine :-)
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace Elca.MvvmHelpers {
public class RadioButtonHelper : DependencyObject {
private static readonly Dictionary<string, List<RadioButton>> s_group2ButtonsMap = new Dictionary<string, List<RadioButton>>();
private static readonly List<RadioButton> s_knownButtons = new List<RadioButton>();
private static void OnRadioButtonChecked(object sender, RoutedEventArgs e) {
RadioButton rb = (RadioButton)sender;
UncheckOtherButtonsInGroup(rb);
}
public static bool? GetIsChecked(RadioButton d) {
return (bool?) d.GetValue(IsCheckedProperty);
}
public static void SetIsChecked(RadioButton d, bool? value) {
d.SetValue(IsCheckedProperty, value);
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked",
typeof(bool?),
typeof(RadioButtonHelper),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Journal |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
IsCheckedChanged));
public static void IsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var rb = d as RadioButton;
if (rb == null) {
throw new Exception("IsChecked attached property only works on a FrameworkElement type");
}
RememberRadioButton(rb);
if ((bool) e.NewValue) {
rb.IsChecked = true; // this triggers OnRadioButtonChecked => other buttons in the same group will be unchecked
}
}
private static void RememberRadioButton(RadioButton rb) {
var groupName = GetGroupName(rb);
// if this button is unknown, add it to the right list, based on its group name
if (s_knownButtons.Contains(rb)) {
return;
}
s_knownButtons.Add(rb);
List<RadioButton> existingButtons;
if (! s_group2ButtonsMap.TryGetValue(groupName, out existingButtons)) {
// unknown group
s_group2ButtonsMap[groupName] = new List<RadioButton> {rb};
RegisterButtonEvents(rb);
} else {
if (! existingButtons.Contains(rb)) {
existingButtons.Add(rb);
RegisterButtonEvents(rb);
}
}
}
private static void RegisterButtonEvents(RadioButton rb) {
rb.Unloaded += OnButtonUnloaded;
rb.Checked += OnRadioButtonChecked;
}
private static void OnButtonUnloaded(object sender, RoutedEventArgs e) {
RadioButton rb = (RadioButton) sender;
ForgetRadioButton(rb);
}
private static void ForgetRadioButton(RadioButton rb) {
List<RadioButton> existingButtons = s_group2ButtonsMap[GetGroupName(rb)];
existingButtons.Remove(rb);
s_knownButtons.Remove(rb);
UnregisterButtonEvents(rb);
}
private static void UnregisterButtonEvents(RadioButton rb) {
rb.Unloaded -= OnButtonUnloaded;
rb.Checked -= OnRadioButtonChecked;
}
private static void UncheckOtherButtonsInGroup(RadioButton rb) {
List<RadioButton> existingButtons = s_group2ButtonsMap[GetGroupName(rb)];
foreach (RadioButton other in existingButtons) {
if (other != rb) {
SetIsChecked(other, false);
}
}
SetIsChecked(rb, true);
}
private static string GetGroupName(RadioButton elt) {
string groupName = elt.GroupName;
if (String.IsNullOrEmpty(groupName)) {
groupName = "none"; // any value will do
}
return groupName;
}
}
}
In the view, for each button:
<RadioButton MvvmHelpers:RadioButtonHelper.IsChecked="{Binding IsExplicitFileSelected, Mode=TwoWay}">
...
</RadioButton>
The VM has a boolean property for each radio button. One must assign a value to each such property to start the listening process of the attached property.
All buttons without a group name are considered to be part of the same group.