UWP Button click from ListViewItem - c#

I really hope someone with more experience can give me a few pointers.
I have the following setup for a UWP project:
A ListView declared in XAML inside my application page, Tubes.xaml:
<ListView Name="TubesGrid"
ItemsSource="{x:Bind TubeItems, Mode=TwoWay}"
ItemTemplateSelector="{StaticResource TubeTemplateSelector}"
IsItemClickEnabled="True"
ItemClick="TubesGrid_ItemClick"
SelectionChanged="TubesGrid_SelectionChanged">
A UserControl as a template for the ListViewItem (UserControl.Resources):
<local:TubeTemplateSelector x:Key="TubeTemplateSelector"
TubeTemplate="{StaticResource TubeTemplate}">
</local:TubeTemplateSelector>
<DataTemplate x:Key="TubeTemplate" x:DataType="data:Tube">
<local:TubeTemplate HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FavoritesNumber="{x:Bind SpaceLength, Mode=OneWay}"></local:TubeTemplate>
</DataTemplate>
Inside the TubeTemplate I have a button, beside other views:
<Button Name="RemoveTube"
Click="RemoveTube_Click"
<Image
Source="../Assets/xIcon.png"
Stretch="None">
</Image>
</Button>
What I'm trying to achieve:
When I click the ListViewItem I want the ItemClick event to be triggered. This works.
But when I click on the Button that's inside the ListViewItem I want a different event to be triggered inside the main page.
The idea is to click on an item to select it, but when I click the button inside the item, I want that item to be removed.
What are my options?

If looks like are doing this without using viewmodels, then you could add an event to the TubeTemplate control.
public event EventHandler Closed;
When the close button is clicked, you would fire the event.
private void RemoveTube_Click(object sender, RoutedEventArgs e)
{
Closed?.Invoke(this, EventArgs.Empty); // Even better would be to give the item clicked (the data context)
}
Then, from within your MainPage you could subscribe to the event.
<local:TubeTemplate HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Closed="TubeTemplate_Closed">
</local:TubeTemplate>
In the TubeTemplate_Closed method, you could remove the item clicked.
private void TubeTemplate_Closed(object sender, EventArgs e)
{
var element = (FrameworkElement)sender;
var tube = (Tube)element.DataContext;
TubeItems.Remove(tube);
}

The idea is to click on an item to select it, but when I click the button inside the item, I want that item to be removed.
The better way is bind button command property with MainPage command method, and process the data source in the code behind. you could refer the following code.
Code Behind
public sealed partial class MainPage : Page
{
public MainPage()
{
MakeDataSource();
this.InitializeComponent();
DataContext = this;
}
public ObservableCollection<string> Items { get; set; }
private void MakeDataSource()
{
Items = new ObservableCollection<string>() { "Nico","CCor","Jack"};
}
public ICommand BtnCommand
{
get
{
return new CommadEventHandler<object>((s) => BtnClick(s));
}
}
private void BtnClick(object s)
{
Items.Remove(s as string);
}
}
public class CommadEventHandler<T> : ICommand
{
public event EventHandler CanExecuteChanged;
public Action<T> action;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this.action((T)parameter);
}
public CommadEventHandler(Action<T> action)
{
this.action = action;
}
}
Xaml code
Please note we need pass current focus listview item parameter to command method and remove it from data souce.
<Grid HorizontalAlignment="Stretch" x:Name="RootGrid">
<ListView ItemsSource="{Binding Items}" x:Name="MyListView">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
<Button HorizontalAlignment="Right"
Margin="0,0,30,0"
Content="Favorite"
Command="{Binding ElementName=MyListView,Path=DataContext.BtnCommand}"
CommandParameter="{Binding}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>

Related

Create a custom Dropdown button in WPF

Here is my firt question for Stackoverflow, I hope that will be ok!
I'm working on a custom Dropdown Button in WPF, and I would like to add a click event on the buttons "Text1" and "Text2". I have to put this dropdown button in a DLL so I use the WPF CustomControl library. So in the perfect world, I would like to create several methods in the MainWindow.xaml.cs and send the name of the method in a class where the name of the button, the icon , the tooltip, ... that will be used in the generic.xaml to find the method to call.
I hope what I said is clear :3
The purpose of this is to have a reusable dropdown button where I can add some click event in the items when we click on it.
Here is the generic.xaml with my dropdown button :
<Style TargetType="{x:Type local:ButtonDropdown}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ButtonDropdown}">
<mah:DropDownButton Content="{Binding Path=Text, RelativeSource={RelativeSource TemplatedParent}}"
ToolTip="{Binding Path=ToolTip, RelativeSource={RelativeSource TemplatedParent}}"
x:Name="DropDownButton"
Orientation="Vertical"
BorderThickness="0"
ItemsSource="{Binding ItemsSource}">
<mah:DropDownButton.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0" ToolTip="{Binding Tooltip}">
<StackPanel.InputBindings>
<MouseBinding Command="{Binding Path=SomeCommand, RelativeSource={RelativeSource TemplatedParent}}" MouseAction="LeftClick" />
</StackPanel.InputBindings>
<Image Source="{Binding Icon}" Width="16"></Image>
<TextBlock Text="{Binding Text}" x:Name="PART_DropdownButton">
</TextBlock>
</StackPanel>
</DataTemplate>
</mah:DropDownButton.ItemTemplate>
<mah:DropDownButton.Icon>
<Image Source="{Binding Path=Icon, RelativeSource={RelativeSource TemplatedParent}}" Width="32"></Image>
</mah:DropDownButton.Icon>
</mah:DropDownButton>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The call of this custom dropdown in the MainWindow.xaml :
<CustomButton:ButtonDropdown Text="Dropdown"
x:Name="ButtonDropdown"
Icon="Images/Open.png"
ToolTip="TOOLTIP DROPDOWN"
ItemsSource="{Binding Items}"/>
Here is my method OnApplyTemplate I add the line 'TextBlock textblock= GetTemplateChild("PART_DropdownButton") as TextBlock;' after the first answer.
public override void OnApplyTemplate()
{
DropDownButton dropDownButton = GetTemplateChild("DropDownButton") as DropDownButton;
TextBlock textblock= GetTemplateChild("PART_DropdownButton") as TextBlock;
textblock.MouseDown += Method1;
dropDownButton.ItemsSource = DropdownItems;
dropDownButton.Click += ButtonDropdown_Click;
}
And finally the class I have created for items in the dropdown :
public class DropdownItem
{
private string text;
private string icon;
private string tooltip;
private string clickEvent;
}
For the moment I have try with command and mousedown on textblock but don't work :/
Edit : I add the name for the textBlock and I add my method OnApplyTemplate from my ButtonDropdown.cs. The dropDownButton.click is ok but when I try to get the "PART_DropdownButton" that is null. I think because of there is not only one but several textBlock so he don't know which one to take. But that is my problem how to asign a different method on all textblock.mouseDown ? How can we put a different name on all textblock ?
Assuming your Dropdown Button derives from a button control give the DropDown button a name in the xaml file e.g. "PART_DropdownButton". Then reference the name in the code behind in the OnApplyTemplate procedure. Here you can add an event handler trapping your mouse events.
private DropdownButton dropdownbutton = null;
...
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
dropdownbutton = base.GetTemplateChild("PART_DropdownButton") as ToggleButton;
if (dropdownbutton != null)
{
dropdownbutton.MouseDown += MouseDown_Click;
}
else
....;
}
Next write your event handler for MouseDown_Click.
Regards Martin
I finally find something that works like I want !
I add an Icommand in my dropdownItem. That will contain my method.
public class DropdownItem
{
private string text;
private string icon;
private string tooltip;
private string clickEvent;
public ICommand ClickCommand { get; set; }
}
In my MainWindow.xaml.cs I add the command I need.
private ICommand _command1;
private ICommand _command2;
public MainWindow()
{
InitializeComponent();
Items.Add(new DropdownItem("Text1", "Images/Open.png", "Method1", "TEST")
{
ClickCommand = Command1
});
Items.Add(new DropdownItem("Text2", "Images/Open.png", "method2", "TEST2")
{
ClickCommand = Command2
});
ButtonDropdown.DropdownItems = Items;
}
public ICommand Command1
{
get
{
return _command1 = new RelayCommand(Method1);
}
}
public ICommand Command2
{
get
{
return _command2 = new RelayCommand(Method2);
}
}
public void Method1()
{
MessageBox.Show("Method 1");
}
public void Method2()
{
MessageBox.Show("Method 2");
}
And finally I add the call to this method in my generic.xaml
<MouseBinding Command="{Binding ClickCommand}" MouseAction="LeftClick" />
Thanks for your help, that's because of your comments and answers that I understood that I was looking in the bad direction

How to bind hotkeys to generic buttons?

I have a Usercontrol that creates buttons at run-time. I have made the buttons work with command bindings by clicking the buttons is there a way to trigger the buttons using hotkeys e.g ctrl+R and once I get to the TakeC command I need to know what command was pressed?
XAML:
<UserControl.InputBindings>
<KeyBinding Command="{Binding TakeC, Source=self}"
CommandParameter="{Binding }"
Gesture="CTRL+R" />
</UserControl.InputBindings>
<StackPanel Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding CButtons}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Name="CButtonsPanel" CanVerticallyScroll="true" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Name="cTakeC" Content="{Binding Content}" Command="{Binding Path=TakeCCommand}" CommandParameter="{Binding}" Margin="5">
<FrameworkElement.Style>
<MultiBinding Converter="{StaticResource CButtonStyleConverter}">
<MultiBinding.Bindings>
<Binding/>
<Binding Path="IsActive"/>
</MultiBinding.Bindings>
</MultiBinding>
</FrameworkElement.Style>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
C#:
TakeCCommand = new RelayCommand(TakeC);
void TakeC(object parameter)
{
ButtonViewModel<StyledButtonModel> myClass = parameter as ButtonViewModel<StyledButtonModel>;
// All buttons gets here once clicked
// I need to know what key was pressed here
}
public class StyledButtonModel
{
public string Name { get; set; } = "Ctrl+R"
public CButtonStyle Styles { get; set; }
}
KeyBindings will only work when the element to which you have applied them is focused:
Keybindings without any focus
Depending on your requirements, a better approach may be to handle the PreviewKeyDown event for the parent window of the UserControl. Something like this:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
Loaded += UserControl1_Loaded;
Unloaded += UserControl1_Unloaded;
}
private void UserControl1_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= UserControl1_Loaded;
Window window = Window.GetWindow(this);
window.PreviewKeyDown += Window_PreviewKeyDown;
}
private void UserControl1_Unloaded(object sender, RoutedEventArgs e)
{
Unloaded -= UserControl1_Unloaded;
Window window = Window.GetWindow(this);
window.PreviewKeyDown -= Window_PreviewKeyDown;
}
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.R && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.LeftCtrl)))
{
var viewModel = DataContext as YourViewModel;
if (viewModel != null)
viewModel.TakeCCommand.Execute();
}
}
}
Then the "hotkey" will work regardess of what element is currently focused in the window.
You may wrap this in an attached behaviour for reuse across several UserControls but how to this is another story that is not directly related to your actual question.

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.

Connect UIElements dynamically added in WPF using MVVM

I have UserControl with ItemsControl binded to ObservableCollection. DataTemplate in this ItemsControl is a Grid containing TextBox and Button.
Here is some code (Updated):
<UserControl.Resources>
<entities:SeparatingCard x:Key="IdDataSource"/>
</UserControl.Resources>
<ItemsControl ItemsSource="{Binding Cards}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" GotFocus="TextBox_GotFocus" Grid.Row="0" Grid.Column="0"/>
<Button DataContext="{Binding Source={StaticResource IdDataSource}}" Command="{Binding Accept}" Grid.Row="0" Grid.Column="1">Accept</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In model file:
public ObservableCollection<SeparatingCard> Cards { get; set; }
Card class:
class SeparatingCard : INotifyPropertyChanged
{
private string _id;
public string Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("Id");
}
}
public ActionCommand Accept { get; }
public SeparatingCard()
{
Accept = new ActionCommand(AcceptCommandExecute);
}
private void AcceptCommandExecute(object obj)
{
MessageBox.Show(Id);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Cards are added in runtime and I dynamically get a new textbox-button pair in my UserControl. Now in each pair I need to do the folowing things:
- Be able to check if the text in textbox is correct and disable/enable apropriate button.
- On button click get the text from apropriate textbox and process it.
I'd like all of this done via MVVM. But I only came to solution that directly have access to UI and implements only the second task:
private void Button_Click(object sender, RoutedEventArgs e)
{
var text = (((sender as Button).Parent as Grid).Children
.Cast<UIElement>()
.First(x => Grid.GetRow(x) == 0 && Grid.GetColumn(x) == 0) as TextBox).Text;
MessageBox.Show(text);
}
Update
As was suggested I tried to move ICommand logic to SeparatingCard class. Now it's always return null and I can't check what object of SeparatingCard class my command refers to. Updates are in the code above.
Instead of using Button.Click, Use Button.Command, which you can bind to some command in SeparatingCard.
Please have a look in this tutorial:
http://www.codeproject.com/Tips/813345/Basic-MVVM-and-ICommand-Usage-Example
Then, SeparatingCard ViewModel will contain an ICommand object which you can bind to Button.Command.
So if the user clicks the button, the event will be directed to the corresponding SeparatingCard object's command.

Assigning an event or command to a DataTemplate in ResourceDictionary

I have the following class:
public class Day
{
public int Date { get; set; }
public String DayName { get; set; }
public Day()
{
}
public Day(int date, string dayName)
{
Date = date;
DayName = dayName;
CommandManager.RegisterClassCommandBinding(typeof(Day), new CommandBinding(DayClick, new ExecutedRoutedEventHandler(OnExecutedDayClick),
new CanExecuteRoutedEventHandler(OnCanExecuteDayClick)));
}
public static readonly RoutedCommand DayClick = new RoutedCommand("DayClick", typeof(Day));
private static void OnCanExecuteDayClick(object sender, CanExecuteRoutedEventArgs e)
{
((Day)sender).OnCanExecuteDayClick(e);
}
private static void OnExecutedDayClick(object sender, ExecutedRoutedEventArgs e)
{
((Day)sender).OnExecutedDayClick(e);
}
protected virtual void OnCanExecuteDayClick(CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = false;
}
protected virtual void OnExecutedDayClick(ExecutedRoutedEventArgs e)
{
string content = String.Format("Day {0}, which is {1}, was clicked.", Date.ToString(), DayName);
MessageBox.Show(content);
e.Handled = true;
}
}
I am using the following DataTemplate (that is in a ResourceDictionary) to render it:
<DataTemplate DataType="{x:Type local:Day}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle Grid.ColumnSpan="2" x:Name="rectHasEntry" Fill="WhiteSmoke"/>
<TextBlock Grid.Column="0" x:Name="textBlockDayName" Text="{Binding DayName}"
FontFamily="Junction" FontSize="11" Background="Transparent" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,2,0,0"/>
<TextBlock Grid.Column="1" x:Name="textBlockDate" Text="{Binding Date}"
FontFamily="Junction" FontSize="11" Background="Transparent" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,2,0,0"/>
<Rectangle Grid.ColumnSpan="2" x:Name="rectMouseOver" Fill="#A2C0DA" Opacity="0"
Style="{StaticResource DayRectangleMouseOverStyle}">
</Rectangle>
</Grid>
</DataTemplate>
No problems so far, I can get it on screen.
What I want to be able to do is assign a Command, or use an event, so that when the user clicks on the Day it will notify the parent of the Day object that it has been clicked.
I've tried the following:
<Rectangle.CommandBindings>
<CommandBinding Command="{x:Static local:Day.NextDay}"
Executed="{x:Static local:Day.OnExecutedDayClick}"
CanExecute="{x:Static local:Day.OnCanExecuteDayClick}"/>
</Rectangle.CommandBindings>
to try and bind the commands that are in the Day class but it didn't work. I got an error stating:
'ResourceDictionary' root element requires a x:Class attribute to support event handlers in the XAML file. Either remove the event handler for the Executed event, or add a x:Class attribute to the root element.
Which I think means that there is no code-behind file for a ResourceDictionary, or something to that effect.
In any event, I'm not sure if I should be using Commands here, or somehow tying events to the Rectangle in question, or if this is even possible. I've seen various places where it sure looks like it's possible, I'm just unable to translate what I'm seeing into something that actually works, hence this post.
Thanks in advance.
You cann't declare CommandBinding here, in this case you can assign the command here in DataTemplate and declare CommandBinding in your main Window or Page.
Edit:
In this way you can use Commands with your custom control.
Create a custom control and Declare Commands and Command Bindings also inside the control itself as in this Sample.
MyCustomControl.cs
static MyCustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl), new FrameworkPropertyMetadata(typeof(MyCustomControl)));
InitializeCommands();
}
private static RoutedCommand _myCommand;
public static RoutedCommand MyCommand
{
get
{
return _myCommand;
}
}
private static void InitializeCommands()
{
_myCommand = new RoutedCommand("MyCommand", typeof(MyCustomControl));
CommandManager.RegisterClassCommandBinding(typeof(MyCustomControl),
new CommandBinding(_myCommand , OnMyCommandExecute));
}
private static void OnMyCommandExecute(object sender, ExecutedRoutedEventArgs e)
{
MyCustomControl control = sender as MyCustomControl;
if (control != null)
{
//logic for this command
}
}
and in your generic.xaml write this style and assign commands like this:
generic.xaml
<Style TargetType="{x:Type local:MyCustomControl}">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyCustomControl}">
<Grid>
<RepeatButton Command="{x:Static local:MyCustomControl.MyCommand}" >Click</RepeatButton>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Categories