Finding the list of checked item in list of lists - wpf

Let's say I have a questionnaire app, which consists of an ItemsControl with a list of controls each consisting of a Label and a ListBox. The items in each ListBox are checkboxes or radiobuttons or whatever.
My question is: When a checkbox is checked, how do I figure out which Question the checkbox applies to? Should I put a reference to the Question in the Tag property? If so, how would I do that?
The Tag binding code below doesn't work. It binds to the ListBoxItem. How do I bind it to the ItemsControl item?
MainWindow.xaml:
<Window x:Class="ListWithinListTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ResourceDictionary>
<Style x:Key="ConditionCheckBoxListStyle" TargetType="{x:Type ListBox}">
<Setter Property="SelectionMode" Value="Multiple" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<CheckBox IsChecked="{Binding IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"
Click="CheckBoxClicked"
Tag="{Binding RelativeSource={RelativeSource TemplatedParent}}"
>
<ContentPresenter></ContentPresenter>
</CheckBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<ItemsControl Name="QuizControl" ItemsSource="{Binding QuizQuestions}" ScrollViewer.CanContentScroll="False">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10 0 10 10" VerticalAlignment="Top">
<Label Content="{Binding Text}" />
<ListBox ItemsSource="{Binding Options}"
DisplayMemberPath="Text"
Tag="{Binding RelativeSource={RelativeSource AncestorType=ItemsControl}}"
Style="{StaticResource ConditionCheckBoxListStyle}"
/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
MainWindow.xaml.cs:
using System.Windows;
using System.Windows.Controls;
namespace ListWithinListTest
{
public class Option
{
public string Text { get; set; }
public bool IsSelected { get; set; } = false;
}
public class Question
{
public string Text { get; set; }
public Option[] Options { get; set; }
}
public class ViewModel
{
public Question[] QuizQuestions { get; set; }
public ViewModel()
{
QuizQuestions = new Question[] {
new Question { Text = "How are you?", Options = new Option[] { new Option { Text = "Good" }, new Option { Text = "Fine" } } },
new Question { Text = "How's your dog?", Options = new Option[] { new Option { Text = "Sleepy" }, new Option { Text = "Hungry" } } },
};
}
}
public partial class MainWindow : Window
{
private ViewModel viewModel;
public MainWindow()
{
InitializeComponent();
this.DataContext = viewModel = new ViewModel();
}
private void CheckBoxClicked(object sender, RoutedEventArgs e)
{
Question question = viewModel.QuizQuestions[???];
}
}
}

Ok, I messed with the Live Visual Tree window until, by process of elimination, I realized that the ListBox is what the Question is going to be bound to. I know now that's a big duh, but that's where I am with this. Then I used RelativeSource AncestorType to find it and the DataSource property to get the question:
MainWindow.xaml:
<CheckBox IsChecked="{Binding IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"
Click="CheckBoxClicked"
Tag="{Binding RelativeSource={RelativeSource AncestorType=ListBox}}"
>
MainWindow.xaml.cs:
private void CheckBoxClicked(object sender, RoutedEventArgs e)
{
CheckBox checkBox = (CheckBox)sender;
ListBox listBox = (ListBox)checkBox.Tag;
Question question = (Question)listBox.DataContext;
Debug.WriteLine(question.Text);
}

You have binded QuizQuestions to the QuizControl, you can get it back from the ItemsSource property.
var questions = (Question[]) QuizControl.ItemsSource;
EDIT
It looks like you got the answer yourself, just another way I would like to suggest to your original question:
Create one more property to your Option class
public class Option
{
public string Text { get; set; }
public bool IsSelected { get; set; } = false;
public int Index{ get; set; }
}
And then add Index to each of your question options.
QuizQuestions = new Question[] {
new Question { Text = "How are you?", Options = new Option[] { new Option { Text = "Good", Index = 0 }, new Option { Text = "Fine", Index = 0 } } },
new Question { Text = "How's your dog?", Options = new Option[] { new Option { Text = "Sleepy", Index = 1 }, new Option { Text = "Hungry", Index = 1 } } },
};
In your CheckBox event you can get the Option Index
private void CheckBoxClicked(object sender, RoutedEventArgs e)
{
var s = (CheckBox)sender;
var op = (Option)s.Tag;
Question question = viewModel.QuizQuestions[op.Index];
}

Related

In WPF,How to add radio button , checkbox, comboBox based on the type which is configured in the database?

Totally i am new to WPF, I need to solve the problem. Could anybody give me a sample xaml code without using code-behind.
Based on the questType (Check,radio, combo) Control need to be created based on the ObserverableCollection.
public class Order
{
public int OrderCode {get;set;}
public string Description {get;set;}
public ObserverableCollection<question> Questions{get;set;}
}
public class question
{
public string questType {get;set;}
public string Question {get;set;}
public ObserverableCollection<Answer> Answers {get;set;}
}
public class Answer
{
public string Ans{get; set;}
}
Based on the questType (Check,radio, combo)
Control need to be created based on the ObserverableCollection.
example:
1001 Pencil Gender? oMale oFemale oOther
[]Checkbox1 []Checkbox2
1002 Pen Fasting? oYes oNo
Here is how I would do it:
Code behind:
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
namespace Ans
{
public class Order
{
public int OrderCode { get; set; }
public string Description { get; set; }
public ObservableCollection<Question> Questions { get; set; }
}
public class Question
{
public string questType { get; set; }
public string Label { get; set; }
public ObservableCollection<Answer> Answers { get; set; }
}
public class Answer
{
public string Ans { get; set; }
public bool IsSelected { get; set; }
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
#region Order
/// <summary>
/// Order Dependency Property
/// </summary>
public static readonly DependencyProperty OrderProperty =
DependencyProperty.Register("Order", typeof(Order), typeof(MainWindow),
new FrameworkPropertyMetadata((Order)null));
/// <summary>
/// Gets or sets the Order property. This dependency property
/// indicates ....
/// </summary>
public Order Order
{
get { return (Order)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
#endregion
public MainWindow()
{
InitializeComponent();
Order = new Order()
{
Questions = new ObservableCollection<Question>()
{
new Question()
{
questType = "Combo",
Label = "Combo",
Answers = new ObservableCollection<Answer>()
{
new Answer(){Ans = "Female"},
new Answer(){Ans = "Male"}
}
},
new Question()
{
questType = "Check",
Label = "Multi",
Answers = new ObservableCollection<Answer>()
{
new Answer(){Ans = "Female"},
new Answer(){Ans = "Male"}
}
},
new Question()
{
questType = "Radio",
Label = "Radio",
Answers = new ObservableCollection<Answer>()
{
new Answer(){Ans = "Female"},
new Answer(){Ans = "Male"}
}
}
}
};
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
foreach(Question q in Order.Questions)
{
Console.WriteLine( q.Label + " : " + string.Join(", " , q.Answers.Where(a=>a.IsSelected).Select(a=>a.Ans)) );
}
}
}
}
XAML:
<Window x:Class="Ans.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Ans"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate x:Key="ComboQuestion">
<ComboBox ItemsSource="{Binding Answers}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Ans}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="CheckQuestion">
<ItemsControl ItemsSource="{Binding Answers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Ans}" IsChecked="{Binding IsSelected, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
<DataTemplate x:Key="RadioQuestion">
<ItemsControl ItemsSource="{Binding Answers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Ans}" IsChecked="{Binding IsSelected, Mode=TwoWay}" GroupName="{Binding DataContext.Label, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Order.Questions}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Label"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Label}"/>
<ContentControl x:Name="ccQuestion" Grid.Column="1" Content="{Binding}" Margin="10"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding questType}" Value="Combo">
<Setter TargetName="ccQuestion" Property="ContentTemplate" Value="{StaticResource ComboQuestion}"/>
</DataTrigger>
<DataTrigger Binding="{Binding questType}" Value="Check">
<Setter TargetName="ccQuestion" Property="ContentTemplate" Value="{StaticResource CheckQuestion}"/>
</DataTrigger>
<DataTrigger Binding="{Binding questType}" Value="Radio">
<Setter TargetName="ccQuestion" Property="ContentTemplate" Value="{StaticResource RadioQuestion}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Order" Click="Button_Click" VerticalAlignment="Bottom"/>
</Grid>
</Window>
The only thing I've added to your model is IsSelected property that allows to know if this answer was selected.
The other important thing is the Radios. Their GroupName property defines the scope. So if no GroupName is set then when clicking on a radio in one question it will unselect radio in another question. I used Question label in my solution however it only works if labels are unique.
Another point is that data triggers are OK if you have 3-5 question types and if thay are only based on the questionType. However for more complex scenarios you can look for ItemTemplateSelector. It allows to write C# code that will select the template based on each item in ItemsControl.

Using DataGridComboBoxColumn as autocompletecombobox in a DataGrid

I want to use the DataGridComboBoxColumn as a autocomplete combobox.
I've got it partially working. When the Row is in EditMode I can type text in the ComboBox, also in ViewMode the control returns the text. Only how to get the Label (in template) to EditMode by mouse doubleclick?
Up front, I don't want to use the DataGridTemplateColumn control because it just doesn't handle keyboard and mouse entry like the DataGridComboBoxColumn does (tabs, arrows, edit/view mode/ double click etc..).
It looks like:
I fixed it adding a behavior to the TextBox to get a link to the parent DataGrid then setting the Row into Edit Mode by calling BeginEdit().
The solution I used:
View
<Window x:Class="WpfApplication1.MainWindow"
xmlns:local="clr-namespace:WpfApplication1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding Model.Things}" Name="MyGrid" ClipboardCopyMode="IncludeHeader">
<DataGrid.Resources>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Object" MinWidth="140" TextBinding="{Binding ObjectText}" ItemsSource="{Binding Source={StaticResource proxy}, Path=Data.Model.ObjectList}" >
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="IsEditable" Value="True"/>
<Setter Property="Text" Value="{Binding ObjectText}"/>
<Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBox IsReadOnly="True" Text="{Binding Path=DataContext.ObjectText, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}}">
<TextBox.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="local:CellSelectedBehavior.IsCellRowSelected" Value="true"></Setter>
</Style>
</TextBox.Resources>
</TextBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridComboBoxColumn.ElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Model
public class Model : BaseModel
{
//List of objects for combobox
private List<string> _objectList;
public List<string> ObjectList { get { return _objectList; } set { _objectList = value; } }
//Rows in datagrid
private List<Thing> _things;
public List<Thing> Things
{
get { return _things; }
set { _things = value; OnPropertyChanged("Things"); }
}
}
public class Thing : BaseModel
{
//Text in combobox
private string _objectText;
public string ObjectText
{
get { return _objectText; }
set { _objectText = value; OnPropertyChanged("ObjectText"); }
}
}
ViewModel
public class ViewModel
{
public Model Model { get; set; }
public ViewModel()
{
Model = new WpfApplication1.Model();
Model.ObjectList = new List<string>();
Model.ObjectList.Add("Aaaaa");
Model.ObjectList.Add("Bbbbb");
Model.ObjectList.Add("Ccccc");
Model.Things = new List<Thing>();
Model.Things.Add(new Thing() { ObjectText = "Aaaaa" });
}
}
Behavior
public class CellSelectedBehavior
{
public static bool GetIsCellRowSelected(DependencyObject obj) { return (bool)obj.GetValue(IsCellRowSelectedProperty); }
public static void SetIsCellRowSelected(DependencyObject obj, bool value) { obj.SetValue(IsCellRowSelectedProperty, value); }
public static readonly DependencyProperty IsCellRowSelectedProperty = DependencyProperty.RegisterAttached("IsCellRowSelected",
typeof(bool), typeof(CellSelectedBehavior), new UIPropertyMetadata(false, OnIsCellRowSelected));
static void OnIsCellRowSelected(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
TextBox item = depObj as TextBox;
if (item == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool)e.NewValue)
item.MouseDoubleClick += SelectRow;
else
item.MouseDoubleClick -= SelectRow;
}
static void SelectRow(object sender, EventArgs e)
{
TextBox box = sender as TextBox;
var grid = box.FindAncestor<DataGrid>();
grid.BeginEdit();
}
}
Helper (to find DataGrid)
public static class Helper
{
public static T FindAncestor<T>(this DependencyObject current) where T : DependencyObject
{
current = VisualTreeHelper.GetParent(current);
while (current != null)
{
if (current is T)
{
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
};
return null;
}
}

WPF: How to use Style.Triggers

I want to implement (file) Explorer like icon display. The items have date and label.
User should be able to edit the label:
Select an item
Click on label
Label's TextBlock is replaced with TextBox for editing
How to end editing (just for info):
Click anywhere outside of the TextBox
Press Enter keyboard key (by implementing ICommand?)
1st I tried to set the Visibility of TextBlock and TextBox in code found out it is not the 'right' way to to do. Maybe it is possible to edit item's Label using (Data)Triggers?
I can track the OnClickLabelBlock and set selectedMedia.IsEditing = true; but it does not fire the trigger.
Any idea why MediaItem.IsEditing property value change is notifying the DataTrigger? Is it something to do with the order of execution or priority mechanism?
I will pick the answer which guides me to the 'best' architecture to solve it.
Thanks.
XAML:
<Window x:Class="WPFComponents.DailyImages"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Model="clr-namespace:WPFComponents.Model"
Title="Media Items" Height="300" Width="300">
<ListView x:Name="_mediaItemList" ItemsSource="{Binding MediaItems}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" SelectionMode="Multiple">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate DataType="Model:MediaItem">
<Grid Width="80" Margin="4">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Image HorizontalAlignment="Center" Stretch="Uniform" Source="{Binding Path=IconPath}" Width="70" />
<StackPanel Grid.Row="2">
<TextBlock Text="{Binding Path=Date}" TextWrapping="Wrap" />
<TextBlock x:Name="_labelTextBlock" Text="{Binding Path=Label}" TextWrapping="Wrap"
PreviewMouseLeftButtonDown="OnClickLabelBlock">
</TextBlock>
<TextBox x:Name="_labelTextBox" Text="{Binding Path=Label}" Visibility="Collapsed"
TextWrapping="WrapWithOverflow" TextAlignment="Center">
</TextBox>
</StackPanel>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsEditing}" Value="True">
<Setter TargetName="_labelTextBlock" Property="Visibility" Value="Collapsed" />
<Setter TargetName="_labelTextBox" Property="Visibility" Value="Visible" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" VerticalAlignment="Top" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
Source:
public partial class DailyImages
{
public DailyImages()
{
InitializeComponent();
ViewModel.DailyImages dailyImages = new ViewModel.DailyImages();
// DailyImages has ObservableCollection<MediaItem> MediaItems property
_mediaItemList.DataContext = dailyImages;
}
private void OnClickLabelBlock(object sender, MouseButtonEventArgs e)
{
TextBlock notes = sender as TextBlock;
if (notes == null)
return;
MediaItem selectedMedia = notes.DataContext as MediaItem;
if (selectedMedia == null)
{
// TODO: Throw exception
return;
}
_mediaItemList.SelectedItems.Clear();
selectedMedia.IsSelected = true;
selectedMedia.IsEditing = true;
}
public class MediaItem
{
public MediaItem()
{
IsEditing = false;
IsSelected = false;
}
public DateTime Date { get; set; }
public string Label { get; set; }
public string IconPath { get; set; }
public bool IsEditing { get; set; }
public bool IsSelected { get; set; }
}
References:
Dependency Property Value Precedence
Part II: ListView & File Explorer Like Behaviour
MediaItem must implement INotifyPropertyChanged and each of its properties that must be bound, must call RaisePropertyChanged in order for the binding to work correctly. In your case, the Binding on IsEditing has no way to know that the value has changed.
To bind your IsEditing property, WPF has to be notified when it is modified.
Then you have to implement INotifyPropertyChanged in MediaItem. (Or add dependency properties)
public class MediaItem : INotifyPropertyChanged
{
public MediaItem()
{
IsEditing = false;
IsSelected = false;
}
// Use the same pattern for Date, Label & IconPath if these value may change after the MediaItem instance has been added to the collection MediaItems.
public DateTime Date { get; set; }
public string Label { get; set; }
public string IconPath { get; set; }
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set
{
if (isSelected != value)
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
private bool isEditing;
public bool IsEditing
{
get { return isEditing; }
set
{
if (isEditing != value)
{
isEditing = value;
OnPropertyChanged("IsEditing");
}
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Otherwise, your code is correct.

Binding a property to the change the listbox items foreground

I have the following ViewModel and I'd like to bind the HotkeysForeground to change the color in the ListBox.
namespace Monkey.Core.ViewModel
{
using System;
using System.Collections.ObjectModel;
using System.Windows.Media;
using Monkey.Core.SystemMonitor.Accessibility;
using Monkey.Core.SystemMonitor.Input;
public class MainWindowViewModel : WorkspaceViewModel
{
private readonly FocusManager _focusManager;
private readonly HotkeyManager _hotkeyManager;
private readonly ObservableCollection<string> _hotkeys;
private Brush _foregroundColor;
private string _title;
public MainWindowViewModel()
{
_hotkeys = new ObservableCollection<string>();
_hotkeyManager = new HotkeyManager();
_hotkeyManager.NewHotkey += HotkeyManager_NewHotkey;
_focusManager = new FocusManager();
_focusManager.Focus += FocusManager_Focus;
}
public Brush HotkeysForeground
{
get
{
return _foregroundColor;
}
set
{
_foregroundColor = value;
OnPropertyChanged(() => Title);
}
}
public ReadOnlyObservableCollection<string> Hotkeys
{
get
{
return new ReadOnlyObservableCollection<string>(_hotkeys);
}
}
public string Title
{
get
{
return _title;
}
set
{
_title = value;
OnPropertyChanged(() => Title);
}
}
protected override void OnDispose()
{
base.OnDispose();
_hotkeyManager.Dispose();
_focusManager.Dispose();
}
private void FocusManager_Focus(object sender, FocusManagerEventArgs e)
{
Title = e.Title;
}
private void HotkeyManager_NewHotkey(object sender, EventArgs eventArgs)
{
HotkeysForeground = Brushes.Blue;
_hotkeys.Clear();
foreach (var hotkey in _hotkeyManager.GetHotkeys())
{
_hotkeys.Add(hotkey);
}
}
}
}
I want to change the foreground color of the items in the ListBox every time the "HotkeyManager_NewHotkey" is fired, to some reason I can't seems to bind it to the view, I tried multiple things to make it work to no avail.
Here is the View I have.
<Window x:Class="Monkey.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="{Binding Mode=OneWay, Path=Title, UpdateSourceTrigger=PropertyChanged}" Height="200" Width="200" ShowInTaskbar="False" WindowStyle="ToolWindow" Topmost="True" ResizeMode="CanResizeWithGrip" AllowsTransparency="False">
<ListBox
Canvas.Left="110"
Canvas.Top="74"
Name="HotkeyList"
Height="Auto" Width="Auto" HorizontalContentAlignment="Left"
BorderThickness="0"
ScrollViewer.CanContentScroll="False"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ItemsSource="{Binding Path=Hotkeys}" VerticalAlignment="Stretch" VerticalContentAlignment="Center" FontSize="20">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsEnabled" Value="False" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Window>
I'm fairly new to WPF and haven't really explored bindings into depth so any help is appreciated.
For starters, you're notifying about Title property change in HotkeysForeground. However, this might not be the case.
If fixing that doesn't help, this rather lenghty way should work for you:
change HotkeysForeground property type to string (just store color name)
create static resource brush in XAML, bind it to color name
override listbox item template to something fairly simple (eg. Label) and bind its foreground to previously mentioned bursh
So, applying those changes:
public string HotkeysForeground
{
get { return _foregroundColor; }
set
{
_foregroundColor = value;
// I assume this is some smart workaround to INPC...
OnPropertyChanged(() => HotkeysForeground);
}
}
Now, in XAML you'll have to do this:
<!-- need to import System namespace -->
<Window x:Class="Monkey.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Window.Resources>
<SolidColorBrush Color="{Binding HotkeysForeground}" x:Key="HotkeysBrush"/>
</Window.Resources>
<ListBox ItemsSource="{Binding Path=Hotkeys}">
<ListBox.ItemTemplate>
<!
<DataTemplate DataType="{x:Type sys:String}">
<Label Content="{Binding}"
Foreground="{StaticResource HotkeysBrush}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>

WPF Dynamic menu associated with the object?

I'd like to be able to create dynamic menus associated with certain object. Let’s say, I will have 3 listview container with one style where I also have a Menu. I need to generate different menu items from collection of the RoutetUICommands in relation on each listview. I was trying to solve this puzzle but took me a while and still have trouble making it work. I need to generate object specific menus, an unique menu for each listview. Any ideas are highly appreciated. Thank you!
XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
x:Class="DynamicMenu.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480">
<Window.Resources>
<Style x:Key="ListViewStyleTask" TargetType="{x:Type ListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListView}">
<Grid>
<Menu x:Name="mainMenu">
<MenuItem x:Name="menuItem" Header="Tasks" ItemsSource="{Binding Commands}" >
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding}" />
<Setter Property="Header" Value="{Binding Path=Text}" />
<Setter Property="CommandParameter" Value="{Binding Path=Parameter}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<ListView x:Name="Container1" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" Style="{DynamicResource ListViewStyleTask}">
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
<ListView x:Name="Container2" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" Style="{DynamicResource ListViewStyleTask}">
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
<ListView x:Name="Container3" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" Style="{DynamicResource ListViewStyleTask}">
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
</Grid>
I have added some commands which I need to associate to 3 different Listviews:
public partial class MainWindow : Window
{
// Container 1
public static RoutedUICommand NameCommand = new RoutedUICommand("Name", "NameCommand", typeof(MainWindow));
public static RoutedUICommand StreetCommand = new RoutedUICommand("Street", "StreetCommand", typeof(MainWindow));
public static RoutedUICommand GroupCommand = new RoutedUICommand("Add to Group", "AddGroup", typeof(MainWindow));
// Container 2
public static RoutedUICommand ViewDetailsCommand = new RoutedUICommand("View Details", "ViewDetailsCommand", typeof(MainWindow));
// Container 3
public static RoutedUICommand StartCommand = new RoutedUICommand("Start", "StartCommand", typeof(MainWindow));
public static RoutedUICommand StopCommand = new RoutedUICommand("Stop", "StopCommand", typeof(MainWindow));
public static RoutedUICommand LoadCommand = new RoutedUICommand("Load", "LoadCommand", typeof(MainWindow));
public MainWindow()
{
this.InitializeComponent();
// Insert code required on object creation below this point.
}
}
}
You need to define a structure to group the data as you need in your control template. Something like this,
public class CommandCollection {
public ObservableCollection<Command> Commands { get; set; }
}
public class Command {
public ICommand Action { get; set; }
public string Text { get; set; }
public string Parameter { get; set; }
}
Have 3 members of CommandCollection each one having its commands and then assign those as datacontext to the ListViews
Updated,
After declaring the above structure you declare 3 members,
public CommandCollection Container1Commands { get; set; }
public CommandCollection Container2Commands { get; set; }
public CommandCollection Container3Commands { get; set; }
Fill these members,
Container1Commands = new CommandCollection ();
Container1Commands.Commands = new ObservableCollection<CommandParameters> ();
Container1Commands.Commands.Add (new CommandParameters () { Action = NameCommand, Text = "Name" });
Container1Commands.Commands.Add (new CommandParameters () { Action = StreetCommand, Text = "Street" });
Container1Commands.Commands.Add (new CommandParameters () { Action = GroupCommand, Text = "Group" });
Container2Commands = new CommandCollection ();
Container2Commands.Commands = new ObservableCollection<CommandParameters> ();
Container2Commands.Commands.Add (new CommandParameters () { Action = ViewDetailsCommand, Text = "ViewDetails" });
Container3Commands = new CommandCollection ();
Container3Commands.Commands = new ObservableCollection<CommandParameters> ();
Container3Commands.Commands.Add (new CommandParameters () { Action = StartCommand, Text = "Start" });
Container3Commands.Commands.Add (new CommandParameters () { Action = StopCommand, Text = "Stop" });
Container3Commands.Commands.Add (new CommandParameters () { Action = LoadCommand, Text = "Load" });
Set data context,
this.DataContext = this;
this.Container1.DataContext = Container1Commands;
this.Container2.DataContext = Container2Commands;
this.Container3.DataContext = Container3Commands;
Update your control template to specify menu item command binding,
<Setter Property="Command" Value="{Binding Action}" />
Updated
XAML
<Window.Resources>
<Style x:Key="ListViewStyleTask" TargetType="{x:Type ListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListView}">
<Grid>
<Menu x:Name="mainMenu" >
<MenuItem x:Name="menuItem" Header="Tasks" ItemsSource="{Binding Commands}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Action}" />
<Setter Property="Header" Value="{Binding Path=Text}" />
<Setter Property="CommandParameter" Value="{Binding Path=Parameter}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ListView
Grid.Row="0" x:Name="Container1" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" Style="{DynamicResource ListViewStyleTask}">
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
<ListView
Grid.Row="1" x:Name="Container2" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" Style="{DynamicResource ListViewStyleTask}">
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
<ListView
Grid.Row="2" x:Name="Container3" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" Style="{DynamicResource ListViewStyleTask}">
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
</Grid>
Code behind
// Container 1
public static RoutedUICommand NameCommand = new RoutedUICommand ("Name", "NameCommand", typeof (Window1));
public static RoutedUICommand StreetCommand = new RoutedUICommand ("Street", "StreetCommand", typeof (Window1));
public static RoutedUICommand GroupCommand = new RoutedUICommand ("Add to Group", "AddGroup", typeof (Window1));
// Container 2
public static RoutedUICommand ViewDetailsCommand = new RoutedUICommand ("View Details", "ViewDetailsCommand", typeof (Window1));
// Container 3
public static RoutedUICommand StartCommand = new RoutedUICommand ("Start", "StartCommand", typeof (Window1));
public static RoutedUICommand StopCommand = new RoutedUICommand ("Stop", "StopCommand", typeof (Window1));
public static RoutedUICommand LoadCommand = new RoutedUICommand ("Load", "LoadCommand", typeof (Window1));
public Window1 () {
InitializeComponent ();
this.Loaded += new RoutedEventHandler (Window1_Loaded);
}
public CommandCollection Container1Commands { get; set; }
public CommandCollection Container2Commands { get; set; }
public CommandCollection Container3Commands { get; set; }
void Window1_Loaded (object sender, RoutedEventArgs e) {
Container1Commands = new CommandCollection ();
Container1Commands.Commands = new ObservableCollection<Command> ();
Container1Commands.Commands.Add (new Command () { Action = NameCommand, Text = "Name" });
Container1Commands.Commands.Add (new Command () { Action = StreetCommand, Text = "Street" });
Container1Commands.Commands.Add (new Command () { Action = GroupCommand, Text = "Group" });
Container2Commands = new CommandCollection ();
Container2Commands.Commands = new ObservableCollection<Command> ();
Container2Commands.Commands.Add (new Command () { Action = ViewDetailsCommand, Text = "ViewDetails" });
Container3Commands = new CommandCollection ();
Container3Commands.Commands = new ObservableCollection<Command> ();
Container3Commands.Commands.Add (new Command () { Action = StartCommand, Text = "Start" });
Container3Commands.Commands.Add (new Command () { Action = StopCommand, Text = "Stop" });
Container3Commands.Commands.Add (new Command () { Action = LoadCommand, Text = "Load" });
this.CommandBindings.Add (new CommandBinding (NameCommand, ExecuteNameCommand, CanExecuteNameCommand));
this.CommandBindings.Add (new CommandBinding (StreetCommand, ExecuteStreetCommand, CanExecuteStreetCommand));
this.CommandBindings.Add (new CommandBinding (GroupCommand, ExecuteGroupCommand, CanExecuteGroupCommand));
this.DataContext = this;
this.Container1.DataContext = Container1Commands;
this.Container2.DataContext = Container2Commands;
this.Container3.DataContext = Container3Commands;
}
private void ExecuteNameCommand (object inSender, RoutedEventArgs inE) {
MessageBox.Show ("Name command Executed");
}
private void CanExecuteNameCommand (object inSender, CanExecuteRoutedEventArgs inE) {
inE.CanExecute = true;
}
private void ExecuteStreetCommand (object inSender, RoutedEventArgs inE) {
MessageBox.Show ("Street command Executed");
}
private void CanExecuteStreetCommand (object inSender, CanExecuteRoutedEventArgs inE) {
inE.CanExecute = true;
}
private void ExecuteGroupCommand (object inSender, RoutedEventArgs inE) {
MessageBox.Show ("Group command Executed");
}
private void CanExecuteGroupCommand (object inSender, CanExecuteRoutedEventArgs inE) {
inE.CanExecute = true;
}
Other classes
public class CommandCollection {
public ObservableCollection<Command> Commands { get; set; }
}
public class Command {
public ICommand Action { get; set; }
public string Text { get; set; }
public string Parameter { get; set; }
}
I Hope now you get it working.
Updated for RoutedUICommand description,
The idea should be to have these menu items in the outer container (like shell) which will have other pages in it (like a frame/canvas), like for example if you see MS Visual Studio the menu items (Save) are part of the application shell and the files openeed are within the shell (shell has a container tabcontrol maybe, where the files are loaded as they are opened). So the routed commands (Save) are defined by the application shell and all the other pages inside the shell's container add those commands in there command binding collection (this.CommandBindings.Add(cmdname, actionname, predicatename)) so each page performs its own respective action and the command is invoked for them only when they are in focus.

Resources