I have this xaml. And I need to uncheck all other checkboxes where one is checked. I other words to allow to check only one. I add TreeViewItems on a runtime.
<TreeView Name="treeView_max" >
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox Name="chk" Margin="2" Tag="{Binding}" Checked="checkBox_Checked">
</CheckBox>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.Resources>
</TreeView>
Adding TreeViewItems at runtime:
foreach (Genesyslab.Desktop.Modules.Core.Model.BusinessAttributes.IDispositionCodeValue item in listOfDispositionCodeValueItemsControl.Items)
{
TreeViewItem newChild2 = new TreeViewItem();
newChild2.Header = item.DisplayName.Remove(0,item.DisplayName.IndexOf("-") + 1);
treeView_max.Items.Add(newChild2);..........`
and
private void checkBox_Checked(object sender, RoutedEventArgs e)
{
try
{
//uncheck all checkboxes except selected one
}
catch (Exception es)
{
MessageBox.Show(es.ToString());
}
}
You can use RadioButton controls that belong to the same group instead, which will get you the behavior of only one option being able to be selected at a time.
Then edit the control template to display CheckBox controls in place of those RadioButton's, and bind the IsChecked property of each CheckBox to its parent RadioButton. Now when you "check" a CheckBox, all other CheckBox controls will become unchecked.
<TreeView Name="treeView_max" >
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<RadioButton Name="chk" Margin="2" Tag="{Binding}" GroupName="SomeGroup">
<RadioButton.Template>
<ControlTemplate>
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=RadioButton}}" />
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.Resources>
</TreeView>
Be careful about where you use this. Users are used to seeing RadioButton's when they're only able to select one option, and CheckBox's where they can select multiple options.
Related
I am having problem that is probably related the collectionview. The problem seem to be that each group will keep track of its own selector, which will prevent the program from firing a new SelectionChanged event when I select a previously selected item in a previous group.
Example:
Group 1:
row1
row2(selected)
Group 2:
row1 (selected)
If I then first open group 1 and click on row 2, then swap to Group 2 and select the first row. If I then again go back to group 1 and clicks row 2 there won't be a trigger event and the selecteditem will stay the same from group 2, until I click row 1.
Which is a problem because there can be cases where there are only 1 row in a group.
My XAML looks like this (removed unrelated stuff).
<DataGrid
Margin="0,10,0,0"
SelectionMode="Single"
IsReadOnly="True"
Name="InvoiceGrid"
SelectedItem="{Binding SelectedDiscrepancy, UpdateSourceTrigger=PropertyChanged}"
RowDetailsVisibilityMode="{Binding RowDetailsVisible}"
ItemsSource="{Binding InvoiceDiscrepancies}">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" BorderBrush="#FF002255" >
<Expander.Header>
<StackPanel Orientation="Horizontal">
<Textblocks for design here.../>
</StackPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<Columns here/>
</DataGrid.Columns>
<DataGrid.RowStyle>
<style for animating the expanding/>
</DataGrid.RowStyle>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Grid>
<ListBox Name="listBox3"
SelectionMode="Single"
HorizontalContentAlignment="Stretch"
ItemTemplate="{StaticResource invoiceItemTemplate}"
ItemsSource="{Binding Invoices, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Path=DataContext.SelectedInvoice, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
</ListBox>
</Grid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
So, anyone know what might be the solution? Is it possible to have one selector between the groups, or should I somehow clear the selector when I select a new row in the parent datagrid?
EDIT:
Github repo with an example of the issue
https://github.com/Snuffsis/GroupingSelect
If you have no needs to keep all the selected listbox items you can just clear the selection.
As an option you can raise PropertyChanged with the null value to clear the current selection.
public Invoice SelectedInvoice
{
get => _selectedInvoice;
set
{
if (_selectedInvoice != value)
{
if (_selectedInvoice != null) // <- add the 'if' block
{
_selectedInvoice = null;
RaisePropertyChanged(() => SelectedInvoice);
}
_selectedInvoice = value;
RaisePropertyChanged(() => SelectedInvoice);
}
}
}
I want to create a Listbox from where it's possible to select elements with checkboxes. It gets the elements through data-binding from a database. The items appear in the Listbox, but when I send the form, the code-behind doesn't receive a SelectedItem value.
The XAML section for the listbox looks like this:
<Grid x:Name="grMozik" Visibility="Visible" Margin="0,0,0,0" DataContext="{Binding}" Grid.Row="3" Grid.ColumnSpan="2">
<ListBox Name="lbMozik" Margin="15" Width="300" Height="200" ItemsSource="{Binding}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Focusable" Value="False"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding MoziNeve}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
And for test purposes the code that would show the selected item looks like this:
string text = ((ListBoxItem)lbMozik.SelectedItem).Content.ToString();
MessageBox.Show(text2);
ListBox.SelectedItem comes from a ListBoxItem which has IsSelected property set to true. If you want to select by CheckBoxes, then bind each of them to owner ListBoxItem.IsSelected:
<CheckBox Content="{Binding MoziNeve}"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"/>
I have a chunk of xaml that duplicates the same pattern six times and would like to reduce that footprint by eliminating the duplication. I'm running into one snag that I need help with.
Background: I have a class that instantiates another class six times (phases of a project as you will).
public ECN(string ecnNumber) {
_ECNNumber = ecnNumber;
//instantiate each phase to be populated or not
_PurchaseParts = new ECNPhase();
_PieceParts = new ECNPhase();
_Weldments = new ECNPhase();
_BOMCfg = new ECNPhase();
_Cleanup = new ECNPhase();
_PRAF = new ECNPhase();
}
Inside each phase is a collection of Changes (another class) referenced in the ECNPhase Class. Each phase has data that is unique to each phase that is shown in a unique view, this is where my snag is which I will show later.
Example of the duplicate xaml Code with the main difference being the different view inside each expander:
<StackPanel Margin="0">
<!--Section for Purchase parts-->
<StackPanel Orientation="Horizontal" >
<CheckBox Margin="0,5,5,5" IsChecked="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts.HasPhase,Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts.HasPhase}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Expander Header="Purchase Parts" Margin="0,0,10,0" Width="110">
<view:PurchasePartsView/>
</Expander>
<CheckBox Content="Submit" Margin="10,5,0,5"/> <!--add a command to handle the submit checkbox event-->
<Label Content="Status:" Margin="10,0,0,0" HorizontalContentAlignment="Right" Width="60"/>
<Label Content="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts.Status}"/>
</StackPanel>
</StackPanel>
<!--Section for Piece Parts-->
<StackPanel Orientation="Horizontal">
<CheckBox Margin="0,5,5,5" IsChecked="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts.HasPhase,Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts.HasPhase}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Expander Header="Piece Parts" Margin="0,0,10,0" Width="110">
<view:PiecePartsView/>
</Expander>
<CheckBox Content="Submit" Margin="10,5,0,5"/> <!--add a command to handle the submit checkbox event-->
<Label Content="Status:" Margin="10,0,0,0" HorizontalContentAlignment="Right" Width="60"/>
<Label Content="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts.Status}"/>
</StackPanel>
</StackPanel>
<!--duplicated four more times-->
</StackPanel>
What I'd like to do is:
<StackPanel>
<view:PhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts}"/>
<view:PhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts}"/>
<!--four more phases-->
</StackPanel>
Where the PhaseView will be the template that handles the duplication and this is where I'm hitting a snag. Each phase needs a unique view (userControl) selected based off of the datacontext of the PhaseView.
<StackPanel Orientation="Horizontal" >
<CheckBox Margin="0,5,5,5" IsChecked="{Binding Path=HasPhase,Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=HasPhase}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Expander Header="DisplayName" Margin="0,0,10,0" Width="110">
<!--add somthing here to select the correct view based on the datacontext-->
<!--<local:PurchasePartsView/> This user control adds a datagrid that is unique to each phase-->
</Expander>
<CheckBox Content="Submit" Margin="10,5,0,5"/> <!--add a command to handle the submit checkbox event-->
<Label Content="Status:" Margin="10,0,0,0" HorizontalContentAlignment="Right" Width="60"/>
<Label Content="{Binding Path=Status}"/>
</StackPanel>
</StackPanel>
I was thinking of using a datatrigger somehow lik shown below, but I haven't had any luck figuring it out. I know there's got to be a way to do this, and it's probably something simple and dumb. Any help would be much appreciated.
<DataTrigger Binding="{Binding Path=DisplayName}" Value="Purchase Parts">
<Setter Property="DataContext" Value="{Binding }"/> <!--Don't know how to bind the DataContext-->
</DataTrigger>
Thanks,
Ok, thanks to Bradley I looked into the DataTemplateSelector and this is what I came up with.
In my UserControl resources I set up several DataTemplates and a reference to the TemplateSelector class that overides the DataTemplateSelector class.
XAML Resources:
<UserControl.Resources>
<local:TemplateSelector x:Key="myTemplateSelector"/>
<DataTemplate x:Key="PurchasePartsTemplate">
<view:PurchasePartsView/>
</DataTemplate>
<DataTemplate x:Key="PiecePartsTemplate">
<view:PiecePartsView/>
</DataTemplate>
<!--Four more templates-->
</UserControl.Resources>
Code Behind for DataTemplateSelector override. Note: I couldn't figure out a way to bind to the ECNPhase class so I bound to the DisplayName property in my class to pull out the correct instance being represented.
class TemplateSelector : DataTemplateSelector {
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
FrameworkElement element = container as FrameworkElement;
if(element != null && item != null && item is string) {
string phase = (string)item;
if(phase == "Purchase Parts") {
return element.FindResource("PurchasePartsTemplate") as DataTemplate;
}else if(phase == "Piece Parts") {
return element.FindResource("PiecePartsTemplate") as DataTemplate;
}
}
return null;
}
}
I'm calling this class in my UserContol like this:
<Expander Header="{Binding Path=DisplayName}" Margin="0,0,10,0" Content="{Binding Path=DisplayName}"
ContentTemplateSelector="{StaticResource myTemplateSelector}"/>
There isn't an items control associated with the expander so I used the content control. I pass the DisplayName into the control property and the contentTemplateSelector uses the myTemplateSelector resource which goes into the codebehind and selects the appropriate datatemplate to use based on the DisplayName.
Now I can call my reusable template like so:
<StackPanel Margin="0">
<view:ChangePhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts}"/>
<view:ChangePhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts}"/>
</StackPanel>
#Bradley, thank you for pointing me in the right direction.
I need to capture the event when user click on empty jumplists, but I've tried the tap event , or put the datatemplate of jumplist in a button and tried click event, but neither of them worked. It's seem like I can't interact with empty section. The Longlistselector only provides 2 method : JumplistOpening and JumplistClosed. So how can I click on the gray jumplist ?
<phone:LongListSelector x:Name="llsMainSong"
IsGroupingEnabled="True" HideEmptyGroups="True"
JumpListStyle="{StaticResource JumpListStyle}"
GroupHeaderTemplate="{StaticResource GroupHeaderTemplate}"
ItemsSource="{Binding GroupedMainSongList}">
.........
</phone:LongListSelector>
<Style x:Key="JumpListStyle" TargetType="phone:LongListSelector">
<Setter Property="GridCellSize" Value="230,113"/>
<Setter Property="LayoutMode" Value="Grid" />
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Button Background="LightGray" Height="113" Margin="6" Click="Button_Click">
<TextBlock Text="{Binding Title}" FontSize="28" Padding="6" VerticalAlignment="Center"
Style="{StaticResource HelveBoldWhite}"/>
</Button>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
private void Button_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("click");
// ^ only jumplist which bind to group has data fired the event.
}
I think it will be very hard for you to read the code but I'll try to do my best !
Here is my xaml code :
<TreeView x:Name="stateMachinesView"
DockPanel.Dock="Top"
SelectedItemChanged="item_Selected"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
BorderThickness="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Value}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<DockPanel>
<DockPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Create Thumbnail"
Click="MenuItemCreate_Click"/>
</ContextMenu>
</DockPanel.ContextMenu>
<Image>
<Image.Style>
<Style TargetType="Image">
<Style.Setters>
<Setter Property="Source"
Value="Resources\state.png"/>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding Item2}"
Value="true">
<Setter Property="Source"
Value="Resources\state_init.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock>
<TextBlock.Text>
<Binding Path="Item1"/>
</TextBlock.Text>
</TextBlock>
</DockPanel>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<DockPanel>
<Image DockPanel.Dock="Left"
Source="Resources\state_machine.png"/>
<TextBlock Text="{Binding Key}"/>
</DockPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The item source of this is a Dictionary<string, ObservableCollection<Tuple<string, bool>>>
Visually, I got something like this:
Now, when I click on my MenuItem I got this code:
private void MenuItemCreate_Click(object sender, RoutedEventArgs e)
{
string stateName =
((sender as FrameworkElement).DataContext as Tuple<string, bool>).Item1;
}
Here I can access to State1_1 with the code above, but now I would like to access to SM1 the parent node !
I tried a lot of things, the closest (to the solution) was this:
DependencyObject parent = VisualTreeHelper.GetParent(sender as DependencyObject);
while (!(parent is TreeViewItem))
parent = VisualTreeHelper.GetParent(parent);
But it doesn't work...
I am, too, thinking about a Template in XAML but im sure I can do it in the code-behind easily!
ContextMenus are not on the same visual tree as the object they are used on. You have go up twice
You need to get up to the ContextMenu, there you can get the TreeViewItem from the ContextMenu.PlacementTarget.
Now you can go up that tree to the parent TreeViewItem.
Of course it would be easier if you just have a reference to the parent in the data items themselves. Also you should not need to acces the TreeViewItems as you usually bind everything as necessarry.