change setter value in style - c#

I'm programming in WPF(c#). I'm trying to change value in a setter of style.
my style is:
<Style TargetType="Control" x:Key="st">
<Setter Property="FontFamily" Value="Tahoma"/>
<Setter Property="FontSize" Value="14"/>
</Style>
and I use it in a button:
<Button x:Name="btnCancel" Style="{StaticResource st}" Content="انصراف" Canvas.Left="30" Canvas.Top="18" Width="139" Height="53" FontFamily="2 badr" FlowDirection="LeftToRight" Click="btnCancel_Click_1" />
and what I try to do is this code:
Style style = new Style();
style = (Style) Resources["st"];
Setter setter =(Setter) style.Setters[1];
setter.Value = 30;
after setting font size to 30 I get this error?
After a “SetterCollectionBase” is in use (sealed), it cannot be modified
How can I solve this problem?

The styles can be set only once (sealed after compiling), you can't change it with code
so the solutions are
create a style by code
Style st = new Style(typeof(System.Windows.Controls.Control));
st.Setters.Add(new Setter(Control.FontFamilyProperty, new FontFamily("Tahoma")));
st.Setters.Add(new Setter(Control.FontSizeProperty, 14.0));
later you can change it
st.Setters.OfType<Setter>().FirstOrDefault(X => X.Property == Control.FontSizeProperty).Value = 30.0;//safer than Setters[1]
or
change the property directly
btnCancel.FontSize=30.0;

Since you are doing pure UI and behind the code while some answers recommend you to use MVVM which will really make a lot of things easier.
Why do you need to manipulate the Style? Is it just for the button and you want to manipulate its FontSize? I assume you are doing this on the Click event of the button where it changes the fontsize.
Try this then
private void btnCancel_Click_1(object sender, RoutedEventArgs e)
{
var button = sender as Button;
if (button != null) button.FontSize = 30;
}

You'll need to create a view model, something like this (I'm using the MVVM Lite class ViewModelBase, you just need something that supports property change notification):
public class MyViewModel : ViewModelBase
{
private double _FontSize = 0.0;
public double FontSize
{
get { return this._FontSize; }
set { this._FontSize = value; RaisePropertyChanged(() => this.FontSize); }
}
}
Then create an instance of it in your window along with a getter:
public partial class Window1 : Window
{
public MyViewModel MyViewModel {get; set;}
public Window1()
{
InitializeComponent();
this.MyViewModel = new MyViewModel { FontSize = 80 };
}
}
And then finally you need to bind your style to use the value in the view model:
<Window.Resources>
<Style TargetType="Control" x:Key="st">
<Setter Property="FontFamily" Value="Tahoma"/>
<Setter Property="FontSize" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}, Path=MyViewModel.FontSize}"/>
</Style>
</Window.Resources>

Related

How to interact with UI elements when using MVVM/MVVMLight in a WPF app

Based on my code below, I want to be able to change the background color of a Button 2 when Button 1 is clicked.
XAML File
<Grid>
<Button x:Name="Button1"
Content="Button 1"
Command="{Binding Button1Command}"/>
<Button x:Name="Button2"
Content="Button 2"/>
</Grid>
ViewModel File
public class MyViewModel : ViewModelBase
{
public ICommand Button1Command{get;private set;}
public MyViewModel(){
Button1Command = new RelayCommand(() => button1_Click());
}
private void button1_Click()
{
Console.WriteLine("Button 1 clicked");
// how can I change the background color of Button 2 here
this.Dispatcher.Invoke(() => {
Button2.Background = Brushes.Red;
});
}
}
In addition to what pm_2 mentioned, you could take advantage of MVVMLight's Messenger class. The VM can send a message that is received by the View to change the background.
public class ChangeBackgroundMessage
{
public Brush TheColor { get; set; }
}
And then in your VM:
Button1Command = new RelayCommand(() => ExecuteButtonCommand());
....
private void ExecuteButtonCommand()
{
Messenger.Default.Send<ChangeBackgroundMessage>(new ChangeBackgroundMessage { TheColor = Brushes.Red } );
}
and in your View:
public partial class MyView : UserControl
{
public MyView()
{
InitializeComponent();
Messenger.Default.Register<ChangeBackgroundMessage>(this, m => ReceiveChangeBackgroundMessage(m);
}
private void ReceiveChangeBackgroundMessage(ChangeBackgroundMessage m)
{
// If you need to ensure this executes only on UI thread, use the
// DispatcherHelper class
DispatcherHelper.CheckBeginInvokeOnUI(() => button2.Background = m.TheColor);
}
}
Yet another alternative would be to have a "view service" that the View registers with it's ViewModel. For example:
public interface IMySpecificViewService
{
void ChangeButtonColor(Brush color);
}
In VM:
public IMySpecificViewService ViewService { get; set; }
and in View
public partial class MyView : UserControl, IMySpecificViewService
...
public MyView()
{
var vm = (MyViewModel)this.DataContext;
vm.ViewService = (IMySpecificViewService)this;
}
public void ChangeButtonColor(Brush color)
{
Button2.Background = color;
}
which can be called in your VM's command handler:
private void ExecuteButtonCommand()
{
ViewService?.ChangeButtonColor(Brushes.Red);
}
I find I use these approaches when I can't directly bind to a property in the VM, (or I don't want to bleed any View specific stuff in the VM) and I need more fine grained control over manipulating the controls.
There are two approaches to this that spring to mind - the first is to simply bind the background colour of Button2 to a property on the viewmodel. You could expose this from the view model as a brush; although the way that is more consistent with MVVM would be to create a value converter.
The idea being that the background of Button2, despite being linked to Button1, is actually linked to a state that has changed when Button1 is pressed; the value converter then maps the state (which is the domain of the ViewModel) with the colour (the domain of the view).
Doing is this way, means that you can change the state in the view model command of button1, but not have to involve the button1_click event, as it is now unnecessary.
This question illustrates how you might achieve this.
First of all you need to declare a property in your view model that will control the background color as well as a command handler which a button can call to toggle it. This might seem a little verbose but you soon get used to that with MVVM, and there are frameworks you can use to minimize that if it really bothers you. So here's the main view model:
public class MainViewModel : ViewModelBase
{
#region Background Color Flag
private bool _Flag;
public bool Flag
{
get { return this._Flag; }
set
{
if (this._Flag != value)
{
this._Flag = value;
RaisePropertyChanged(() => this.Flag);
}
}
}
#endregion Background Color Flag
#region Button Command Handler
private ICommand _ButtonCommand;
public ICommand ButtonCommand
{
get { return this._ButtonCommand = (this._ButtonCommand ?? new RelayCommand(OnButtonPressed)); }
}
private void OnButtonPressed()
{
this.Flag = !this.Flag;
}
#endregion Button Command Handler
public MainViewModel()
{
}
}
One of the objectives of MVVM is to have as loose coupling between the view and the view model as possible. The Button's command binding should be fairly straightforward, but to set the background of the second button you can use DataTriggers:
<StackPanel Orientation="Vertical">
<Button Content="Toggle Background" HorizontalAlignment="Left" VerticalAlignment="Top"
Command="{Binding ButtonCommand}" />
<Button Content="Hello World!" HorizontalAlignment="Left" VerticalAlignment="Top">
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Flag}" Value="False">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Flag}" Value="True">
<Setter Property="Background" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
This will cause the second button's background to toggle between red and green as you click the first button:

WPF&MVVM: access to Controls from RelayCommand()

I need both operating by mouse clicking and operating by hotkeys in my WPF application. User's actions affects on both data and appearance of application controls.
For example, the following app will send data to tea machine. You can select the tea brand, type (hot or cold) and optional ingredients: milk, lemon and syrup.
Not good from the point of view of UI design, but just example:
If to click the dropdown menu or input Ctrl+B, the list of select options will appear.
If to click the "Hot" button on input Ctrl+T, button becomes blue and text becomes "Cold". If to click or input Ctrl+T again, button becomes orange and text becomes to "Hot" again.
If to click optional ingredient button or input respective shortcut, button's background and text becomes gray (it means "unselected"). Same action will return the respective button to active state.
If don't use MVVM and don't define shortcuts, the logic will be relatively simple:
Tea tea = new Tea(); // Assume that default settings avalible
private void ToggleTeaType(object sender, EventArgs e){
// Change Data
if(tea.getType().Equals("Hot")){
tea.setType("Cold");
}
else{
tea.setType("Hot");
}
// Change Button Appearence
ChangeTeaTypeButtonAppearence(sender, e);
}
private void ChangeTeaTypeButtonAppearence(object sender, EventArgs e){
Button clickedButton = sender as Button;
Style hotTeaButtonStyle = this.FindResource("TeaTypeButtonHot") as Style;
Style coldTeaButtonStyle = this.FindResource("TeaTypeButtonCold") as Style;
if (clickedButton.Tag.Equals("Hot")) {
clickedButton.Style = coldTeaButtonStyle; // includes Tag declaration
clickedButton.Content = "Cold";
}
else (clickedButton.Tag.Equals("Cold")) {
clickedButton.Style = hotTeaButtonStyle; // includes Tag declaration
clickedButton.Content = "Hot";
}
}
// similarly for ingredients toggles
XAML:
<Button Content="Hot"
Tag="Hot"
Click="ToggleTeaType"
Style="{StaticResource TeaTypeButtonHot}"/>
<Button Content="Milk"
Tag="True"
Click="ToggleMilk"
Style="{StaticResource IngredientButtonTrue}"/>
<Button Content="Lemon"
Tag="True"
Click="ToggleLemon"
Style="{StaticResource IngredientButtonTrue}"/>
<Button Content="Syrup"
Tag="True"
Click="ToggleSyrup"
Style="{StaticResource IngredientButtonTrue}"/>
I changed my similar WPF project to MVVM because thanks to commands it's simple to assign the shortcuts:
<Window.InputBindings>
<KeyBinding Gesture="Ctrl+T" Command="{Binding ToggleTeaType}" />
</Window.InputBindings>
However, now it's a problem how to set the control's appearance. The following code is invalid:
private RelayCommand toggleTeaType;
public RelayCommand ToggleTeaType {
// change data by MVVM methods...
// change appearence:
ChangeTeaTypeButtonAppearence(object sender, EventArgs e);
}
I need the Relay Commands because I can bind it to both buttons and shortcuts, but how I can access to View controls from RelayCommand?
You should keep the viewmodel clean of view specific behavior. The viewmodel should just provide an interface for all relevant settings, it could look similar to the following (BaseViewModel would contain some helper methods to implement INotifyPropertyChanged etc.):
public class TeaConfigurationViewModel : BaseViewModel
{
public TeaConfigurationViewModel()
{
_TeaNames = new string[]
{
"Lipton",
"Generic",
"Misc",
};
}
private IEnumerable<string> _TeaNames;
public IEnumerable<string> TeaNames
{
get { return _TeaNames; }
}
private string _SelectedTea;
public string SelectedTea
{
get { return _SelectedTea; }
set { SetProperty(ref _SelectedTea, value); }
}
private bool _IsHotTea;
public bool IsHotTea
{
get { return _IsHotTea; }
set { SetProperty(ref _IsHotTea, value); }
}
private bool _WithMilk;
public bool WithMilk
{
get { return _WithMilk; }
set { SetProperty(ref _WithMilk, value); }
}
private bool _WithLemon;
public bool WithLemon
{
get { return _WithLemon; }
set { SetProperty(ref _WithLemon, value); }
}
private bool _WithSyrup;
public bool WithSyrup
{
get { return _WithSyrup; }
set { SetProperty(ref _WithSyrup, value); }
}
}
As you see, there is a property for each setting, but the viewmodel doesn't care about how the property is assigned.
So lets build some UI. For the following example, generally suppose xmlns:local points to your project namespace.
I suggest utilizing a customized ToggleButton for your purpose:
public class MyToggleButton : ToggleButton
{
static MyToggleButton()
{
MyToggleButton.DefaultStyleKeyProperty.OverrideMetadata(typeof(MyToggleButton), new FrameworkPropertyMetadata(typeof(MyToggleButton)));
}
public Brush ToggledBackground
{
get { return (Brush)GetValue(ToggledBackgroundProperty); }
set { SetValue(ToggledBackgroundProperty, value); }
}
// Using a DependencyProperty as the backing store for ToggledBackground. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ToggledBackgroundProperty =
DependencyProperty.Register("ToggledBackground", typeof(Brush), typeof(MyToggleButton), new FrameworkPropertyMetadata());
}
And in Themes/Generic.xaml:
<Style TargetType="{x:Type local:MyToggleButton}" BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyToggleButton}">
<Border x:Name="border1" BorderBrush="Gray" BorderThickness="1" Background="{TemplateBinding Background}" Padding="5">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="border1" Property="Background" Value="{Binding ToggledBackground,RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now, build the actual window content using this toggle button. This is just a rough sketch of your desired UI, containing only the functional controls without labels and explanation:
<Grid x:Name="grid1">
<StackPanel>
<StackPanel Orientation="Horizontal">
<ComboBox
x:Name="cb1"
VerticalAlignment="Center"
IsEditable="True"
Margin="20"
MinWidth="200"
ItemsSource="{Binding TeaNames}"
SelectedItem="{Binding SelectedTea}">
</ComboBox>
<local:MyToggleButton
x:Name="hotToggle"
IsChecked="{Binding IsHotTea}"
VerticalAlignment="Center"
Margin="20" MinWidth="60"
Background="AliceBlue" ToggledBackground="Orange">
<local:MyToggleButton.Style>
<Style TargetType="{x:Type local:MyToggleButton}">
<Setter Property="Content" Value="Cold"/>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="Hot"/>
</Trigger>
</Style.Triggers>
</Style>
</local:MyToggleButton.Style>
</local:MyToggleButton>
</StackPanel>
<StackPanel Orientation="Horizontal">
<local:MyToggleButton
x:Name="milkToggle"
Content="Milk"
IsChecked="{Binding WithMilk}"
VerticalAlignment="Center"
Margin="20" MinWidth="60"
Background="WhiteSmoke" ToggledBackground="LightGreen"/>
<local:MyToggleButton
x:Name="lemonToggle"
Content="Lemon"
IsChecked="{Binding WithLemon}"
VerticalAlignment="Center"
Margin="20" MinWidth="60"
Background="WhiteSmoke" ToggledBackground="LightGreen"/>
<local:MyToggleButton
x:Name="syrupToggle"
Content="Syrup"
IsChecked="{Binding WithSyrup}"
VerticalAlignment="Center"
Margin="20" MinWidth="60"
Background="WhiteSmoke" ToggledBackground="LightGreen"/>
</StackPanel>
</StackPanel>
</Grid>
Notice the style trigger to change the button content between Hot and Cold.
Initialize the datacontext somewhere (eg. in the window constructor)
public MainWindow()
{
InitializeComponent();
grid1.DataContext = new TeaConfigurationViewModel();
}
At this point, you have a fully functional UI, it will work with the default mouse and keyboard input methods, but it won't yet support your shortcut keys.
So lets add the keyboard shortcuts without destroying the already-working UI. One approach is, to create and use some custom commands:
public static class AutomationCommands
{
public static RoutedCommand OpenList = new RoutedCommand("OpenList", typeof(AutomationCommands), new InputGestureCollection()
{
new KeyGesture(Key.B, ModifierKeys.Control)
});
public static RoutedCommand ToggleHot = new RoutedCommand("ToggleHot", typeof(AutomationCommands), new InputGestureCollection()
{
new KeyGesture(Key.T, ModifierKeys.Control)
});
public static RoutedCommand ToggleMilk = new RoutedCommand("ToggleMilk", typeof(AutomationCommands), new InputGestureCollection()
{
new KeyGesture(Key.M, ModifierKeys.Control)
});
public static RoutedCommand ToggleLemon = new RoutedCommand("ToggleLemon", typeof(AutomationCommands), new InputGestureCollection()
{
new KeyGesture(Key.L, ModifierKeys.Control)
});
public static RoutedCommand ToggleSyrup = new RoutedCommand("ToggleSyrup", typeof(AutomationCommands), new InputGestureCollection()
{
new KeyGesture(Key.S, ModifierKeys.Control)
});
}
You can then bind those commands to appropriate actions in your main window:
<Window.CommandBindings>
<CommandBinding Command="local:AutomationCommands.OpenList" Executed="OpenList_Executed"/>
<CommandBinding Command="local:AutomationCommands.ToggleHot" Executed="ToggleHot_Executed"/>
<CommandBinding Command="local:AutomationCommands.ToggleMilk" Executed="ToggleMilk_Executed"/>
<CommandBinding Command="local:AutomationCommands.ToggleLemon" Executed="ToggleLemon_Executed"/>
<CommandBinding Command="local:AutomationCommands.ToggleSyrup" Executed="ToggleSyrup_Executed"/>
</Window.CommandBindings>
and implement the appropriate handler method for each shortcut in the window code behind:
private void OpenList_Executed(object sender, ExecutedRoutedEventArgs e)
{
FocusManager.SetFocusedElement(cb1, cb1);
cb1.IsDropDownOpen = true;
}
private void ToggleHot_Executed(object sender, ExecutedRoutedEventArgs e)
{
hotToggle.IsChecked = !hotToggle.IsChecked;
}
private void ToggleMilk_Executed(object sender, ExecutedRoutedEventArgs e)
{
milkToggle.IsChecked = !milkToggle.IsChecked;
}
private void ToggleLemon_Executed(object sender, ExecutedRoutedEventArgs e)
{
lemonToggle.IsChecked = !lemonToggle.IsChecked;
}
private void ToggleSyrup_Executed(object sender, ExecutedRoutedEventArgs e)
{
syrupToggle.IsChecked = !syrupToggle.IsChecked;
}
Again, remember this whole input binding thing is purely UI related, it is just an alternative way to change the displayed properties and the changes will be transferred to the viewmodel with the same binding as if the user clicks the button by mouse. There is no reason to carry such things into the viewmodel.
how I can access to View controls from RelayCommand?
You shouldn't. The whole point of MVVM (arguably) is to separate concerns. The 'state' that the ViewModel contains is rendered by the View (controls). The ViewModel/logic should never directly adjust the view - as this breaks the separation of concerns and closely couples the logic to the rendering.
What you need is for the view to render how it wants to display the state in the View Model.
Typically, this is done by bindings. As example: Rather than the ViewModel grabbing a text box reference and setting the string: myTextBox.SetText("some value"), we have the view bind to the property MyText in the view model.
It's the view's responsibility to decide how to show things on the screen.
That's all well and good, but how? I suggest, if you want to do this change using styles like you describe, I'd try using a converter that converts the using a binding to ViewModel state (Say, an enum property Hot or Cold):
<Button Content="Hot"
Tag="Hot"
Click="ToggleTeaType"
Style="{Binding TeaType, Converter={StaticResource TeaTypeButtonStyleConverter}}"/>
Note, we're using WPF's bindings. The only reference we've got tot he view model is through it's property TeaType.
Defined in your static resources, we have the converter:
<ResourceDictionary>
<Style x:Key="HotTeaStyle"/>
<Style x:Key="ColdTeaStyle"/>
<local:TeaTypeButtonStyleConverter
x:Key="TeaTypeButtonStyleConverter"
HotStateStyle="{StaticResource HotTeaStyle}"
ColdStateStyle="{StaticResource ColdTeaStyle}"/>
</ResourceDictionary>
And have the logic for converting from the TeaType enum to a Style in this:
public enum TeaType
{
Hot, Cold
}
class TeaTypeButtonStyleConverter : IValueConverter
{
public Style HotStateStyle { get; set; }
public Style ColdStateStyle { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
TeaType teaType = (TeaType)value;
if (teaType == TeaType.Hot)
{
return HotStateStyle;
}
else if (teaType == TeaType.Cold)
{
return ColdStateStyle;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
It could be made more generic and re-usable.
You should also take a look at toggle buttons, they deal with this kind of thing internally.

Specify .XAML part of UserControl in code-behind

I wrote custom TreeView control.
XAML:
<TreeView x:Class="EArchiveMaster.View.MyTreeView"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<EventSetter Event="LostFocus" Handler="EventSetter_OnHandler" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
.cs
public partial class MyTreeView
{
public event Action SomeItemLostFocus;
public MyTreeView()
{
InitializeComponent();
}
private void EventSetter_OnHandler(object sender, RoutedEventArgs e)
{
e.Handled = true;
if (SomeItemLostFocus != null)
SomeItemLostFocus();
}
}
But when I try to use it I got well known error:
Cannot set Name attribute value 'TextBox' on element 'TextBox'. 'TextBox' is under the scope of element 'MyTreeView', which already had a name registered when it was defined in another scope.
I found some receipts how to fix this error. Namely, specify .xaml part of control in its code-behind.
But I have no idea how can I do this.
The code clearly shows you want to extend TreeView. Basically if you want to build control that can hold some content(which can be named...), like ContentControl, ItemsControl, etc.. it is always better to go with CustomControl. UserControl with XAML and CS code is not suitable for this case.
In your case, create a class like below and extend the functionalities,
public class MyTreeView : TreeView
{
public event Action SomeItemLostFocus;
public MyTreeView()
{
DefaultStyleKey = typeof(MyTreeView);
}
public override void OnLostFocus(object sender, RoutedEventArgs e)
{
e.Handled = true;
if (SomeItemLostFocus != null)
SomeItemLostFocus();
}
}
If you want to customize the look and feel, you should override the default Style of the control. This style should be available in generic.xaml file inside Themes folder. More information on Custom Control development is here.
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
I found solution appropriate for me. This it the way how to define Style of TreeViewItem in code, not in XAML. Now I have TreeView definded only in code-behind, therefore, error will not be risen.
public class MyTreeView : TreeView
{
public event RoutedEventHandler ItemLostLogicFocus;
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
var itemContainerStyle = new Style
{
TargetType = typeof(TreeViewItem),
};
#region Binding
var expandedBinding = new Binding("IsExpanded")
{
Mode = BindingMode.TwoWay,
};
var selectedBinding = new Binding("IsSelected")
{
Mode = BindingMode.TwoWay,
};
#endregion
#region Setters
itemContainerStyle.Setters.Add(new Setter
{
Property = TreeViewItem.IsExpandedProperty,
Value = expandedBinding
});
itemContainerStyle.Setters.Add(new Setter
{
Property = TreeViewItem.IsSelectedProperty,
Value = selectedBinding
});
#endregion
#region EventSetters
itemContainerStyle.Setters.Add(new EventSetter
{
Event = LostFocusEvent,
Handler = new RoutedEventHandler(ItemLostLogicFocusHandler)
});
#endregion
ItemContainerStyle = itemContainerStyle;
}
private void ItemLostLogicFocusHandler(Object sender, RoutedEventArgs e)
{
e.Handled = true;
if (ItemLostLogicFocus != null)
ItemLostLogicFocus(sender, e);
}
}

WPF Image with two sources

I'd like to extend Image class by adding second source. I want to define second source in XAML (like original source) and change these images when mouse enters/leaves this image.
I tried myself with:
class MainMenuImageButton : Image
{
public static readonly DependencyProperty Source2Property;
public ImageSource Source2
{
get { return Source2; }
set
{
this.MouseEnter+=new System.Windows.Input.MouseEventHandler(MainMenuImageButton_MouseEnter);
}
}
public void MainMenuImageButton_MouseEnter(object sender, MouseEventArgs e)
{
this.Source = Source2;
}
}
But it doesn't work and I think I do it tottaly wrong. Can somebody help?
[UPDATE]
I wrote this:
class MainMenuImageButton : Image
{
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
var source = (BitmapSource)Source;
var x = (int)(hitTestParameters.HitPoint.X / ActualWidth * source.PixelWidth);
var y = (int)(hitTestParameters.HitPoint.Y / ActualHeight * source.PixelHeight);
var pixels = new byte[4];
source.CopyPixels(new Int32Rect(x, y, 1, 1), pixels, 4, 0);
if (pixels[3] < 10) return null;
return new PointHitTestResult(this, hitTestParameters.HitPoint);
}
public ImageSource Source1
{
get { return GetValue(ImageSourceProperty) as ImageSource; }
set { base.SetValue(ImageSourceProperty, value); }
}
public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("Source1", typeof(ImageSource), typeof(MainMenuImageButton));
public ImageSource Source2
{
get { return GetValue(ImageSource2Property) as ImageSource; }
set { base.SetValue(ImageSource2Property, value); }
}
public static readonly DependencyProperty ImageSource2Property = DependencyProperty.Register("Source2", typeof(ImageSource), typeof(MainMenuImageButton));
public MainMenuImageButton() : base()
{
this.MouseEnter += new MouseEventHandler(MainMenuImageButton_MouseEnter);
this.MouseLeave += new MouseEventHandler(MainMenuImageButton_MouseLeave);
}
void MainMenuImageButton_MouseLeave(object sender, MouseEventArgs e)
{
this.Source = this.Source1;
}
void MainMenuImageButton_MouseEnter(object sender, MouseEventArgs e)
{
this.Source = this.Source2;
}
}
But sometimes it works and sometimes there is exception: "An unhandled exception of type 'System.ArgumentException' occurred in PresentationCore.dll
Additional information: The value is outside the expected range."
I'm not sure if I understood, but I tried this:
class MainMenuImageButton : Image
{
public static readonly DependencyProperty Source2Property = DependencyProperty.Register("Source2", typeof(ImageSource), typeof(MainMenuImageButton), new PropertyMetadata(true));
public ImageSource Source2
{
get { return (ImageSource)GetValue(Source2Property); }
set
{
BitmapImage logo = new BitmapImage(new Uri(value.ToString(), UriKind.Relative));
SetValue(Source2Property, logo);
this.MouseEnter+=new System.Windows.Input.MouseEventHandler(MainMenuImageButton_MouseEnter);
}
}
public void MainMenuImageButton_MouseEnter(object sender, MouseEventArgs e)
{
this.Source = Source2;
}
}
And still nothing. Wham am I doing wrong?
Refer to the Custom Dependency Properties article on MSDN. The event hookup belongs in your dependency property's PropertyChangedCallback.
I would also suggest using a trigger instead of event handling. However, this doesn't mean you will need to duplicate the XAML everywhere you want to use it. You could define a custom control with the image switching trigger in its default style (see "Defining Resources at the Theme Level" in the Control Authoring Overview). Where MouseOverImage is a Control with "Source" and "Source2" dependency properties, you could define this default style:
<Style TargetType="local:MouseOverImage">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MouseOverImage">
<Grid>
<Image Name="SourceImage" Source="{TemplateBinding Source}" />
<Image Name="Source2Image" Source="{TemplateBinding Source2}" Visibility="Hidden" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="SourceImage" Property="Visibility" Value="Hidden" />
<Setter TargetName="Source2Image" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
If you use event handlers, you would need to store the original value of Source, add a MouseLeave handler that reverts it, and also consider the case where a user reassigns Source or Source2 at any time. Using the trigger solution with two separate "Source" and "Source2" bindings, all of this is handled automatically.
EDIT
But sometimes it works and sometimes there is exception: "An unhandled
exception of type 'System.ArgumentException' occurred in
PresentationCore.dll
Additional information: The value is outside the expected range."
My guess is that HitTestCore is firing after the source changes but before it's applied to the layout, so there is a discrepancy between ActualWidth and source.PixelWidth. I am not sure of the rationale for including these in the calculation (shouldn't they always be the same?) Try just using the following:
var x = (int)hitTestParameters.HitPoint.X;
var y = (int)hitTestParameters.HitPoint.Y;
Extending Image Is an overkill, all you have to do is define a style which will use trigger to swap the sources
<Image>
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="Image1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Source" Value="Image2"/>
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
You don't need to extend the Image class to do this. There is a property on the Image class called IsMouseOver that you can trigger on to switch the Source of your image. Put this in a style on your view and you'll be all set.
You need to add the new property as a Dependency Property. You can find out more from the DependencyProperties Overview page at MSDN, but the basic idea is this:
You first create the Dependency Property:
public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register(
"IsSpinning", typeof(Boolean), typeof(YourClassName), new PropertyMetadata(true));
Then you can optionally add a wrapper using standard CLR properties (for your own use only):
public bool IsSpinning
{
get { return (bool)GetValue(IsSpinningProperty); }
set { SetValue(IsSpinningProperty, value); }
}
(Code example was taken from the linked article)

Saving a WPF canvas as an image following MVVM Pattern

I have a canvas, e.g. similar to this solution or many others using the ItemsControl.
Now I want a button which should be bound to an ICommand. This command should call a method of ViewModel class which can save the image.
The saving method is clear, but how do I do the binding following the MVVM pattern?
You could pass the Canvas to the ViewModel's Save method using a CommandParameter
<Button Content="Save"
Command="{Binding SaveCanvasCommand}"
CommandParameter="{Binding ElenementName=myCanvas}" ?>
<Canvas x:Name="myCanvas">
<!-- Stuff to save -->
</Canvas>
And somewhere in you ViewModel or Command you'd have
void SaveCanvasCommandExecute(object parameter)
{
UIElement toSave = (UIElement)parameter;
//.. You'd probably use RenderTargetBitmap here to save toSave.
}
If you don't want to reference UI elements in your ViewModel you could use an attached behaviour:
internal static class Behaviours
{
public static readonly DependencyProperty SaveCanvasProperty =
DependencyProperty.RegisterAttached("SaveCanvas", typeof(bool), typeof(Behaviours),
new UIPropertyMetadata(false, OnSaveCanvas));
public static void SetSaveCanvas(DependencyObject obj, bool value)
{
obj.SetValue(SaveCanvasProperty, value);
}
public static bool GetSaveCanvas(DependencyObject obj)
{
return (bool)obj.GetValue(SaveCanvasProperty);
}
private static void OnSaveCanvas(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
// Save code.....
}
}
}
Then in your ViewModel you have your Command that sets a property, also on your ViewModel:
public ICommand SaveCanvasCommand
{
get
{
if (_saveCanvasCommand == null)
_saveCanvasCommand = new RelayCommand(() => { IsSaveCanvas = true; });
return _saveCanvasCommand;
}
}
And the property which is bound to your View:
public bool IsSaveCanvas
{
get { return _isSaveCanvas; }
set
{
_isSaveCanvas = value;
RaisePropertyChanged("IsSaveCanvas");
}
}
Then hooking it all up in the Xaml looks like this:
Add a Trigger on the Control that binds the value of your ViewModel property to your attached behaviour:
<UserControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSaveCanvas}" Value="True">
<Setter Property="wpfApplication1:Behaviours.SaveCanvas" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSaveCanvas}" Value="False">
<Setter Property="wpfApplication1:Behaviours.SaveCanvas" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
And then bind your Button / MenuItem to the ViewModels Save Command:
<Canvas.ContextMenu>
<MenuItem Header="Save" Command="{Binding SaveCanvasCommand}"/>
</Canvas.ContextMenu>

Categories