I have a context menu and at certain point i disable some of the commands bound to context menu items.
what works:
when the command gets disabled , clicking on the menu item does not call the execute method on the command as i wanted.
What does not work:
Both commands that can exucute and those which cant look identical!!
my users cant tell the difference.
Problem:
How can I change the style to display this change. Change in background color, border color, foreground color , a tooltip... anything will be acceptable..
I experimented with xaml and was unable to solve this.
(I am a noob with xaml so my attempts are not even worth pasting here :) )
xaml :
<Border BorderThickness="1" MinWidth="100" Background="Transparent" ContextMenu="{Binding Path=ContextMenu}" BorderBrush="{Binding Path=BorderColor}">
Binding:
public override ContextMenu ContextMenu
{
get
{
return new ContextMenu
{
ItemsSource = new ObservableCollection<MenuItem>
{
new MenuItem
{
Header = IsSharedFieldView? "Delete Shared Field" :"Delete Field" ,
Command = DeleteFieldCommand
}
}
};
}
}
The trick here, is that ContextMenu actually inherits from ItemsControl, this works on my machine:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
MenuItems = new ObservableCollection<KeyValuePair<string, ICommand>>();
MenuItems.Add(new KeyValuePair<string, ICommand>("One", OneCommand));
MenuItems.Add(new KeyValuePair<string, ICommand>("Two", null));
}
public ObservableCollection<KeyValuePair<String, ICommand>> MenuItems { get; set; }
#region OneCommand
DelegateCommand _OneCommand;
public DelegateCommand OneCommand
{
get { return _OneCommand ?? (_OneCommand = new DelegateCommand(One, CanOne)); }
}
public bool CanOne()
{
return false;
}
public void One()
{
}
#endregion
}
And the XAML:
<Window x:Class="DynamicContextMenuTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Path Data="M 10,2 L 17.5,2 L 17.5,0 L 23,4.5 L 17.5,9 L 17.5,7.3 L 10,7.3 L 10,2" Fill="Green">
<Path.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuItems}" DisplayMemberPath="Key">
<ContextMenu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Value}" />
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</ContextMenu.Resources>
</ContextMenu>
</Path.ContextMenu>
</Path>
</Grid>
</Window>
Notice a few things:
You no longer bind to a ContextMenu (viewmodels shouldn't be aware of controls), instead you bind to a collection of string(Header)/ICommand(Command)
When an item's command's CanExecute returns false, the bound control's IsEnabled property becomes false. You can have a trigger on that property to modify the appearance of the bound control.
<Window.Resources>
<Style TargetType="{x:Type MenuItem}">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="Black"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TextBox >
<TextBox.ContextMenu>
<ContextMenu>
<Menu>
<MenuItem Header="Add" IsEnabled="False"/>
<MenuItem Header="Delete"/>
</Menu>
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</Grid>
Its just demo and you can have idea from this . and you can bind IsEnabled to your VM Command.CanExecute . I hope this will help.
Solution that worked :
Initially i was binding a ContextMenu from my view model and the isEnabled was not working.
Instead of that I created the context menu in xaml and bound the itemsSource from the viewModel.
Now the menu item gets disabled and the triggers are working.
Not sure what i was doing wrong but this fixed it :)
Related
I have a TemplatedControl SoftwareReleaseControl, which displays some texts and a button. I need this button to inherit its Click event from the property OnInstallClick that is specified when creating the SoftwareReleaseControl control.
The problem is: I can't make it work. It does not bind to the template's property. I've tried copying source code from Avalonia's button (ClickEvent) to the control's Code-Behind. It shows as an EventHandler, but is not passed to the button, and also gives an Unable to find suitable setter or adder [...] error.
SoftwareReleaseControl.xaml:
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="using:Updater.Controls">
<Design.PreviewWith>
<StackPanel Spacing="5">
<Panel Classes="Spacing"/>
<my:SoftwareReleaseControl Title="..." Version="..." Description="..." Installed="..."/>
<my:SoftwareReleaseControl Title="..." Version="..." Description="..." Installed="..."/>
</StackPanel>
</Design.PreviewWith>
<Style Selector="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource text}"/>
<Setter Property="FontFamily" Value="Lato"/>
</Style>
[...]
<Style Selector="my|SoftwareReleaseControl">
[...]
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Panel Background="{TemplateBinding Background}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Height="{TemplateBinding Height}"
MinWidth="{TemplateBinding MinWidth}" Width="{TemplateBinding Width}">
<Grid Margin="{TemplateBinding Padding}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
RowDefinitions="46, *, 40">
[...]
<StackPanel Grid.Row="2" Orientation="Horizontal">
======= HERE ===>> <Button x:Name="PART_footer_installButton"
Content="Instalar"
======= PROBLEM ===>> Click="{TemplateBinding OnInstallClick}">
<Button.Styles>
<Style Selector="Button#PART_footer_installButton">
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Foreground" Value="{DynamicResource text}"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Tag" Value="{TemplateBinding Tag}"/>
</Style>
<Style Selector="Button#PART_footer_installButton:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource text}"/>
</Style>
</Button.Styles>
</Button>
</StackPanel>
</Grid>
</Panel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Styles>
SoftwareReleaseControl.xaml.cs (code-behind):
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using System;
namespace Updater.Controls
{
public partial class SoftwareReleaseControl : TemplatedControl
{
public SoftwareReleaseControl()
{
}
public static readonly RoutedEvent<RoutedEventArgs> OnInstallClickEvent = RoutedEvent.Register<Button, RoutedEventArgs>(nameof(OnInstallClick), RoutingStrategies.Bubble);
public event EventHandler<RoutedEventArgs> OnInstallClick
{
add => AddHandler(OnInstallClickEvent, value);
remove => RemoveHandler(OnInstallClickEvent, value);
}
[...]
}
}
MainWindow.xaml (where I'm trying to show the controls):
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Updater.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:my="using:Updater.Controls"
mc:Ignorable="d" Width="800" Height="520"
x:Class="Updater.Views.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="Updater">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Background="{DynamicResource window.background}"
RowDefinitions="40, *" ColumnDefinitions="*">
[...]
<ScrollViewer Grid.Row="1">
<StackPanel Spacing="5" x:Name="stk_releases">
<Panel Classes="Spacing"/>
==== HERE ===>> <my:SoftwareReleaseControl Title="..." Version="..." Description="..." Installed="..." OnInstallClick="{Binding btn_OnClick}"/>
<my:SoftwareReleaseControl Title="..." Version="..." Description="..." Installed="..." OnInstallClick="{Binding btn_OnClick}"/>
</StackPanel>
</ScrollViewer>
</Grid>
</Window>
It doesn't really matter if I will need to implement btn_OnClick in the MainWindow's code-behind or in the ViewModel.
The current code is giving me the errors:
Unable to find suitable setter or adder for property OnInstallClick of type Updater:Updater.Controls.SoftwareReleaseControl for argument Avalonia.Markup:Avalonia.Data.Binding, available setter parameter lists are: System.EventHandler[[Avalonia.Interactivity.RoutedEventArgs, Avalonia.Interactivity]] on MainWindow.xaml, on the OnInstallClick="{Binding btn_OnClick}".
Unable to find suitable setter or adder for property Click of type Avalonia.Controls:Avalonia.Controls.Button for argument Avalonia.Base:Avalonia.Data.IBinding, available setter parameter lists are: System.EventHandler1<Avalonia.Interactivity.RoutedEventArgs> on *SoftwareReleaseControl.xaml*, on the Click="{TemplateBinding OnInstallClick}".
Yes, I did specify the StyleInclude on App.xaml.
Why Click and not Command: I need the sender object so I can get the Tag property of the button. There will be many of this control on the window and I need to sort out which one got clicked.
tl;dr: How can I specify an event handler on my templated control, in a way that the button inside it can inherit the handler as its Click (not Command). Where will I need to implement the handler? ViewModel or CodeBehind?
I gave up on using Click, and instead found a way to send the button itself to a Command.
SoftwareReleaseControl.xaml:
[...]
<Button x:Name="PART_footer_installButton"
Command="{Binding _OnInstallClick}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}">
<Button.Styles>
<Style Selector="Button#PART_footer_installButton">
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Foreground" Value="{DynamicResource text}"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Tag" Value="{TemplateBinding Tag}"/>
</Style>
<Style Selector="Button#PART_footer_installButton:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource text}"/>
</Style>
</Button.Styles>
</Button>
[...]
SoftwareReleaseControl.xaml.cs:
namespace Updater.Controls
{
public partial class SoftwareReleaseControl : TemplatedControl
{
public SoftwareReleaseControl()
{
DataContext = this;
}
public event EventHandler InstallClick;
private void _OnInstallClick(object? sender)
{
EventHandler handler = InstallClick;
handler?.Invoke(sender, EventArgs.Empty);
}
[...]
}
}
MainWindow.xaml.cs:
namespace Updater.Views
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel(this);
for (int i = 0; i < 6; i++)
{
var t = new SoftwareReleaseControl();
t.Description = (string)App.Current.Resources["lorem.50"];
t.Installed = i % 2 == 0;
t.Tag = i;
t.InstallClick += T_InstallClick;
stk_releases.Children.Add(t);
}
}
private void T_InstallClick(object? sender, EventArgs e)
{
Debug.WriteLine("123");
if (sender is Button btn)
{
Debug.WriteLine(btn.Tag);
}
}
}
}
I am trying to create a generic status indicator display for a WPF HMI application. These status indicators are a user control wherein two concentric circles of different radius overlap. I want to be able to change the colour of the "fill" property on the path tag depending on some dependency properties of my StatusIndicator class. In practice, there are an arbitrary number of these indicators that may be used. The 'state' of these indicators is handled by a class object, DigitalIOAssignment, which gets its data (componentID, isActive, isInterlocked, etc.) from a PLC concerning the state of a given I/O component. Since the number of these status indicators is arbitrary, I create a List <DigitalIOAssignment> and pass this to my viewmodel. This is working correctly and I can see the data I want to bind correctly in my viewmodel.
The status indicator is coded as follows:
XAML:
<UserControl x:Class="HMI.UserControls.StatusIndicator"
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:prism="http://prismlibrary.com/"
xmlns:local="clr-namespace:HMI.UserControls"
xmlns:globals="clr-namespace:HMI.LogixPLCService.Globals;assembly=HMI.LogixPLCService"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="100">
<Viewbox x:Name="ControlViewbox" Stretch="Uniform" Height="auto" Width="auto">
<Canvas x:Name="ControlCanvas" Width="100" Height="100">
<!-- Draw Secondary Indicator Body First -->
<Path x:Name="StatusIndicator_Body" Width="100" Height="100"
Canvas.Left="0" Canvas.Top="0" StrokeThickness="1"
StrokeMiterLimit="2.75" Stroke="Black">
<Path.Data>
<EllipseGeometry Center="50,50" RadiusX="50" RadiusY="50"/>
</Path.Data>
<Path.Style>
<Style TargetType="Path">
<Setter Property="Fill" Value="LightGray"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:StatusIndicator}}, Path=isInterlockedProperty}"
Value="True">
<Setter Property="Fill" Value="Yellow"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
<!-- Draw Foreground Indicator Body Second -->
<Path x:Name="StatusIndicator_Status" Width="100" Height="100"
Canvas.Left="0" Canvas.Top="0" StrokeThickness=".5"
StrokeMiterLimit="1" Stroke="Black">
<Path.Data>
<EllipseGeometry Center="50,50" RadiusX="30" RadiusY="30"/>
</Path.Data>
<Path.Style>
<Style TargetType="Path">
<Setter Property="Fill" Value="DarkGray"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:StatusIndicator}}, Path=isActiveProperty}"
Value="True">
<Setter Property="Fill" Value="Lime"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Canvas>
</Viewbox>
</UserControl>
Code Behind:
namespace HMI.UserControls
{
public partial class StatusIndicator : UserControl
{
/// <summary>
/// Interaction logic for StatusIndicator.xaml
///</summary>
public string StatusIndicatorName
{
get { return (string)GetValue(StatusIndicatorNameProperty); }
set { SetValue(StatusIndicatorNameProperty, value); }
}
public static readonly DependencyProperty StatusIndicatorNameProperty =
DependencyProperty.Register("StatusIndicatorName",
typeof(string), typeof(StatusIndicator), new PropertyMetadata(null));
public string ComponentID
{
get { return (string)GetValue(ComponentIDProperty); }
set { SetValue(ComponentIDProperty, value); }
}
public static readonly DependencyProperty ComponentIDProperty =
DependencyProperty.Register("ComponentID",
typeof(string), typeof(StatusIndicator), new PropertyMetadata(null));
public bool isActiveProperty
{
get { return (bool)GetValue(isActive); }
set { SetValue(isActive, value); }
}
public static readonly DependencyProperty isActive =
DependencyProperty.Register("isActiveProperty",
typeof(bool), typeof(StatusIndicator), new PropertyMetadata(false));
public bool isInterlockedProperty
{
get { return (bool)GetValue(isInterlocked); }
set { SetValue(isInterlocked, value); }
}
public static readonly DependencyProperty isInterlocked =
DependencyProperty.Register("isInterlockedProperty",
typeof(bool), typeof(StatusIndicator), new PropertyMetadata(false));
public StatusIndicator()
{
InitializeComponent();
}
}
}
In my view's xaml, I create each status indicator in the designer and hard-code a x:Name to it and assign this to StatusIndicatorName since I can't figure out how to pass this Name value at runtime to the code-behind (any hints would be appreciated!!). What I want to do is this:
Create a StatusIndicator user control and assign the StatusIndicatorName property a known string
UserControls:StatusIndicator.ComponentID property is bound to DigitalIOAssignment.componentID
It is my hope that binding to the List causes an iteration over this list and to engage a <DataTrigger> that will allow me to reference the same DigitalIOAssignment object when the trigger condition is met, and set the appropriate flags (isActive, isInterlocked etc) in this way. This pseudocode represents, I hope, what I am trying to do in my view's Xaml:
<UserControls:StatusIndicator x:Name="DI_99VLV01"
StatusIndicatorName="{Binding ElementName=DI_99VLV01}"
Height="18" Width="18"
Margin="106,144,0,0"
HorizontalAlignment="Left" VerticalAlignment="Top"
ComponentID="{Binding privateDigitalInputAssignments/componentID}">
<DataTrigger Binding="{Binding Path=(UserControls:StatusIndicator.ComponentID)}"
Value="{Binding Path=(UserControls:StatusIndicator.StatusIndicatorName)}">
<Setter Property="UserControls:StatusIndicator.isActiveProperty"
Value="{Binding privateDigitalInputAssignments/isActive}"/>
<Setter Property="UserControls:StatusIndicator.isInterlockedProperty"
Value="{Binding privateDigitalInputAssignments/isInterlocked}"/>
</DataTrigger>
</UserControls:StatusIndicator>
Obviously, this implementation does not work. I cannot use a binding for a value on a data trigger (I may have to hard-code the component ID I am expecting since I hard-code the status indicator name anyway), and I cannot seem to use setters for my dependency properties. I get an error:
Cannot find the static member 'isActivePropertyProperty' [sic!] on the type 'StatusIndicator'.
Can someone please give me some insight how to approach this problem for what I am trying to achieve? Even if I need to start over and approach it a different way? Thank you!
I'm not 100% sure I follow what you're after. You have an arbitrary number of DigitalIOAssignment's which are held in a List in the VM, and you want to create a StatusIndicator in the view for each of them?
The usual way to do this is use an ItemsControl in the view, with a DataTemplate that has a single StatusIndicator. If you bind ItemsControl.ItemsSource to your list, wpf will apply the template for every item in the list, and the DataContext of the template will be that item, so you can do straight bindings with no need for triggers.
Something like:
<ItemsControl ItemsSource="{Binding DigitalInputAssignments}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<UserControls:StatusIndicator Height="18" Width="18"
Margin="106,144,0,0"
HorizontalAlignment="Left" VerticalAlignment="Top"
StatusIndicatorName="{Binding Name}"
ComponentID="{Binding ComponentID}"
IsActive="{Binding IsActive}"
IsInterlocked="{Binding IsInterlocked}">
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I want to be able to access the index of a ListBox that is inside of another ListBox and increment that index. I tried to use ItemContainerGenerator but when I cast the Item as a ListBox or ItemsControl it returns null.
I would like to increment the index in code behind or a viewmodel.
here is the basic structure of my template
<Window x:Class="WpfApplication12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="MyListStyle" TargetType="{x:Type ListBox}">
<Setter Property="BorderThickness" Value="0"></Setter>
<Setter Property="SelectedIndex" Value="0"></Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal">
</VirtualizingStackPanel>
</ItemsPanelTemplate >
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Visibility" Value="Collapsed"></Setter>
<!--<Setter Property="Margin" Value="2" />-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<ListBox Name="InnerList" ItemsSource="{Binding}" ></ListBox>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Visibility" Value="Visible"/>
</Trigger>
</Style.Triggers>
</Style>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Button Grid.Row="1" Click="Button_Click">button</Button>
<ListBox Style="{StaticResource MyListStyle}" Name="ListItemsControl" VirtualizingPanel.IsVirtualizing="True" Grid.Row="0"></ListBox>
</Grid>
</Window>
Here is some code to load the list
public MainWindow()
{
InitializeComponent();
CompositeCollection cc = new CompositeCollection();
cc.Add(new List<int>() { 1, 2, 3, 4, 5 });
cc.Add(new List<int>() { 6, 7, 8, 9, 10 });
cc.Add(new List<int>() { 11, 12, 13, 14, 15 });
ListItemsControl.ItemsSource = cc;
}
I recommend you use a breakpoint and walk through the visualizer (little magnifying glass icon if you want over a variable) so that you may get an idea of how this code works.
Place this into your button event handler:
private void Button_Click(object sender, RoutedEventArgs e)
{
//var item = ListItemsControl.ItemContainerGenerator.ContainerFromIndex(1) as ListBoxItem;
//var innerListBox = VisualTreeHelper.GetChild(item, 0) as ListBox;
//innerListBox.SelectedIndex++;
// For every item in the ListItemsControl
for (int i = 0; i < ListItemsControl.Items.Count; i++)
{
// Get the item container for the specified index and cast it as ListBoxItem.
var item = ListItemsControl.ItemContainerGenerator.ContainerFromIndex(i)
as ListBoxItem;
// Then, get the first child of the ListBoxItem and cast it as a ListBox.
// Note that I'm making an assumption that it'll always be a ListBox,
// which is why you should perform some checks in a production case,
// to avoid exceptions.
var innerListBox = VisualTreeHelper.GetChild(item, 0) as ListBox;
// Lastly, I increment the index of this ListBox.
innerListBox.SelectedIndex++;
}
}
Commented out is the way of changing index of just one element. Underneath, I'm incrementing indexes of all three inner list boxes. This gives you an idea of how to get to them, so from thereon you may alter it to your liking. Obviously, you may want to add code to check for null and confirm the correct type before trying to increment the SelectedIndex property, but that's not very difficult.
Old Answer (based on first post):
This is a code-behind example. Let me know if you want an MVVM one. You may also use Binding to SelectedIndex property, but then you would have to ensure that you have INotifyPropertyChanged implemented.
XAML:
<Window x:Class="LB.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="219.965" Width="217.535">
<StackPanel>
<ListBox x:Name="lbOuter" HorizontalContentAlignment="Stretch">
<ListBox.Items>
<TextBlock>Outer Item #1</TextBlock>
<TextBlock>Outer Item #1</TextBlock>
<ListBox x:Name="lbInner" BorderBrush="Black" BorderThickness="1" Margin="5">
<ListBox.Items>
<TextBlock>Inner Item #1</TextBlock>
<TextBlock>Inner Item #2</TextBlock>
<TextBlock>Inner Item #3</TextBlock>
</ListBox.Items>
</ListBox>
<TextBlock>Outer Item #3</TextBlock>
<TextBlock>Outer Item #4</TextBlock>
</ListBox.Items>
</ListBox>
<StackPanel Orientation="Horizontal">
<Button Content="Increment Outer" Margin="5" Click="Button_Click"/>
<Button Content="Increment Inner" Margin="5" Click="Button_Click_1"/>
</StackPanel>
</StackPanel>
</Window>
Code-Behind:
using System.Windows;
namespace LB
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (lbOuter.SelectedIndex < (lbOuter.Items.Count - 1))
{
lbOuter.SelectedIndex++;
}
else
{
lbOuter.SelectedIndex = 0;
}
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
if (lbInner.SelectedIndex < (lbInner.Items.Count - 1))
{
lbInner.SelectedIndex++;
}
else
{
lbInner.SelectedIndex = 0;
}
}
}
}
The above code will actually loop your selection. So, if you reach the end, it'll take you to index 0. You may remove that, if you don't want that functionality.
how to talk toolbar (it is a user control) on the button to enable a wait cursor.
i have a ViewModel is inherited from viewmodelBase. But i can not use IsWorking on toolbar.
Below code is toolbar's code. i clicked select button. data is selecting from database. Cursor must be turn to wait.after Selecting, Cursor must return normal.
<Button x:Name="Select"
Content="select"
Command="{Binding SelectCommand }">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Cursor" Value="Arrow"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsWorking}" Value="True">
<Setter Property="Cursor" Value="Wait"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
ViewModelBase.cs: there is no inheritance with toolbar. it is a basemodel.
private bool _isWorking = false;
public bool IsWorking
{
get { return _isWorking; }
set
{
_isWorking = value;
OnPropertyChanged("IsWorking");
}
}
Here is the code from the view-model:
public class MainViewModel : ViewModelBase
{
public void Select()
{
IsWorking = true; cursor turn to wait mode
// db Process...
IsWorking = false; cursor turn to hand mode
}
}
How to communicate with toolbar from ViewModel? Click select Cursor must be turn Wait mode. after selection, cursor must be hand(default).
Changing the cursor in WPF sometimes works, sometimes doesn't
From what I see, your problem is that you're trying to bind from your UserControl back to the view/window in which it's located.
The usercontrol, of course, will not be able to bind like this.
You have a few options:
1 . Give the UserControl the View's datacontext:
<local:UserControl1 DataContext="{Binding ElementName=MyWindow}" />
and then in your UserControl you can bind to the ViewModel's IsWorking directly:
<DataTrigger Binding="{Binding IsWorking}" Value="True">
<Setter Property="Cursor" Value="Wait"/>
</DataTrigger>
2 .
Create a Dependency Property on your UserControl and bind to it from the view:
In your usercontrol create a new DP:
public bool MyIsWorking
{
get { return (bool)GetValue(MyIsWorkingProperty ); }
set { SetValue(MyIsWorkingProperty , value); }
}
public static readonly DependencyProperty MyIsWorkingProperty =
DependencyProperty.Register("MyIsWorking", typeof(bool), typeof(UserControl1), new UIPropertyMetadata(false));
In the usercontrol's XAML bind to the DP:
<DataTrigger Binding="{Binding MyIsWorking}" Value="True">
<Setter Property="Cursor" Value="Wait"/>
</DataTrigger>
In your window - bind the DP to the VM's IsWorking property:
<local:UserControl1 MyIsWorking="{Binding IsWorking, ElementName=MyWindow}" />
3 . Finally this will work but it's not recommended!!!**
<DataTrigger Binding="{Binding IsWorking, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}" Value="True">
<Setter Property="Cursor" Value="Wait"/>
</DataTrigger>
What this does is tries to find the Window in the Visual Tree and use its DataContext. Why isn't it recommended? Because you might not be using this in a Window or you might not want it to be bound to the specific DataContext the containing Window is using. Either way, it IS another possibility.
I have a WPF datagrid that has two associated buttons for editing and deleting data. Ideally, I'd like to disable or make these buttons invisible when an item is not selected in the grid. How do I approach this?
If it matters my datagrid XAML is:
<DataGrid AutoGenerateColumns="True" Margin="10,174,12,35" Name="dataGridArchiveQueue" Visibility="Visible" AlternatingRowBackground="#01000000" BorderBrush="#FF688CAF"
HorizontalGridLinesBrush="#37000000" VerticalGridLinesBrush="#37000000" CanUserAddRows="False" CanUserDeleteRows="False" IsReadOnly="True"
SelectedItem="{Binding SelectedItemArchiveGrid}" Grid.ColumnSpan="2">
<DataGrid.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="LightBlue" />
</DataGrid.Resources>
</DataGrid>
You can use DataTrigger on button to handle the disable/visible of buttons OR you can write the logic on CanExecuteChange event of command which gets binded to button
Data Trigger
<Button.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{SelectedItemArchiveGrid}" Value="{x:Null}">
<Setter Property="IsEnabled"Value="False"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
Command
> public RelayCommand<object> DeletCommand { get; set; }
>
> DeletCommand = new RelayCommand<object>(OnDelete, OnDeletCanExecute);
>
> private void OnDelete(object obj) { }
>
> private bool OnDeletCanExecute(object obj) {
return SelectedItemArchiveGrid != null; }
XAML
<Button Content="Delete" Command="{Delete Command}"/>
You can change the Data Template for the Currently Selected Item.