0

I am learning on how to create custom control in WPF. I have few problems that I am stuck with.

Basically, I am trying to create custom control for navigation bar that has two level.

  • Level 1 contains a big icons with a title text; and
  • Level 2 contains a small icons where the user can click on it and event will be generated.

This is what I am trying to archieve:

--------------------------------
|                              |
|  ICON     TITLE 1            |
|                              |
|      small icon     option 1 |
|      small icon     option 2 |
|      small icon     option 3 |
|                              |
|                              |
|  ICON     TITLE 2            |
|                              |
|      small icon     option 1 |
|      small icon     option 2 |
|      etc...                  |
|                              |
--------------------------------

Here is my Generic.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Global.WPFs.GUIs">
    <Style TargetType="{x:Type local:GNavBar}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:GNavBar}">
                    <ScrollViewer x:Name="PART_Scroll"
                                  Background="{TemplateBinding Background}"
                                  BorderBrush="{TemplateBinding BorderBrush}"
                                  BorderThickness="{TemplateBinding BorderThickness}"
                                  HorizontalAlignment="Stretch"
                                  VerticalAlignment="Stretch"
                                  VerticalScrollBarVisibility="Auto"
                                  Focusable="False">
                        <ItemsControl x:Name="PART_Items">
                            <ItemsControl.ItemContainerStyle>
                                <Style TargetType="ContentPresenter">
                                    <Setter Property="Margin" Value="0"/>
                                    <Setter Property="Control.Padding" Value="0 8 0 2"/>
                                    <Setter Property="Control.HorizontalContentAlignment" Value="Stretch"/>
                                </Style>
                            </ItemsControl.ItemContainerStyle>
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <StackPanel VerticalAlignment="Center" HorizontalAlignment="Stretch">
                                        <Grid>
                                            <Grid.RowDefinitions>
                                                <RowDefinition Height="76"></RowDefinition>
                                            </Grid.RowDefinitions>
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition Width="76" />
                                                <ColumnDefinition Width="*" />
                                            </Grid.ColumnDefinitions>
                                            <Image Grid.Column="0" Source="{Binding ImgSrc}" Width="72" Height="72" HorizontalAlignment="Center" VerticalAlignment="Center"></Image>
                                            <TextBlock Grid.Column="1" Text="{Binding Text}" Margin="4 0 0 0" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="20"></TextBlock>
                                        </Grid>
                                        <ListBox ItemsSource="{Binding Items}"
                                                 BorderThickness="0"
                                                 Background="Transparent" 
                                                 ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                                                 ScrollViewer.VerticalScrollBarVisibility="Disabled">
                                            <ListBox.ItemContainerStyle>
                                                <Style TargetType="ListBoxItem">
                                                    <Setter Property="Margin" Value="0"/>
                                                    <Setter Property="Control.Padding" Value="0"/>
                                                </Style>
                                            </ListBox.ItemContainerStyle>
                                            <ListBox.ItemTemplate>
                                                <DataTemplate>
                                                    <Border>
                                                        <Grid>
                                                            <Grid.RowDefinitions>
                                                                <RowDefinition Height="36"></RowDefinition>
                                                            </Grid.RowDefinitions>
                                                            <Grid.ColumnDefinitions>
                                                                <ColumnDefinition Width="16" />
                                                                <ColumnDefinition Width="16" />
                                                                <ColumnDefinition Width="*" />
                                                            </Grid.ColumnDefinitions>
                                                            <Image Grid.Column="1" Source="{Binding ImgSrc}" Width="16" Height="16" HorizontalAlignment="Center" VerticalAlignment="Center"></Image>
                                                            <TextBlock Grid.Column="2" Text="{Binding Text}" Margin="4 0 0 0" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="16"></TextBlock>
                                                        </Grid>
                                                    </Border>
                                                </DataTemplate>
                                            </ListBox.ItemTemplate>
                                        </ListBox>
                                    </StackPanel>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                        </ItemsControl>
                    </ScrollViewer>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Everything is working fine, but I have two problems:

  1. How do I go about with detecting which item is clicked so I can raise the event to the parent class?
  2. Scrolling works fine if I scroll in TITLE, but as soon as the mouse pointer hit listbox, the scrolling stops working.

Thanks...


This is what I'm trying to achieve: Sample NavBar

Sam
  • 1,826
  • 26
  • 58
  • `ListBox` will consume the scroll event, as it contains a list of items, so you need to handle the scroll event and set the e.Handled = false. For the events of being clicked then you need to attach an event handler to Image and TextBlock. That could be done through Style and an event setter. – XAMlMAX Nov 25 '17 at 17:04
  • @XAMlMAX, I know that, I've google it. hehehe ;) but how to handle the event inside a template? – Sam Nov 27 '17 at 02:50
  • I see. Another option would be to use `TreeView`. Which would do everything you need to achieve. – XAMlMAX Nov 27 '17 at 09:52
  • @XAMlMAX, how to detect which `node` (eg. `item in the treeview`) has been clicked (so that I can use it to raise event to calling function) inside a template? Also, is it possible to use TreeView to display different size of icons (eg. title icon is a big icon 64x64 where as small icon is 16x16)? – Sam Nov 28 '17 at 12:06
  • Do you pictures for when a node is collapsed and when its expanded? And what sort of function you would want to call inside of the `DataTemplate`? – XAMlMAX Nov 28 '17 at 12:55
  • @XAMlMAX, I don't quite have the screen shot. It is just in my head at the moment. LOL... But, I'll try to mock it up later today. What I need (the most important) is that when the user click on the "option" item, I need to be able to raise the event so that the calling class can capture it and display the option GUI appropriately. The question is, `how to capture this click event because it doesn't allow event handler in the template?` – Sam Nov 29 '17 at 03:41
  • Look into event setters and hide or display parts of the data template as you see fit. **`THERE IS NO NEED TO CALL ANY METHOD`** on click. – XAMlMAX Nov 29 '17 at 08:37
  • @XAMlMAX, I don't quite understand. For example, if "Option 1" is clicked, I want to display a message box "Option 1 clicked!". How do I do it (because, it doesn't allow event capture in template)? – Sam Nov 29 '17 at 09:04
  • You can use an event setter for that and assign an event handler. You really need to provide more information about what you want to do cause I think all you need is a `TreeView` in there which would make this a lot cleaner xaml. I will create an answer in about an hour as I am quite busy at work ATM. – XAMlMAX Nov 29 '17 at 09:27
  • @XAMlMAX, I've included the screenshot. So, my first trouble is when the user click on "Option 1", it should display a message box `Option 1 is clicked!`. Any idea how to tackle this problem? – Sam Nov 29 '17 at 10:17
  • I am at work behind a proxy so I can't view it. I would have to have a look when I get home. – XAMlMAX Nov 29 '17 at 10:19
  • Thank you @XAMlMAX, it is very kind of you... :) – Sam Nov 29 '17 at 10:20
  • Hi Sorry, been busy for past few days. But I have found a [perfect example of TreeView usage](https://stackoverflow.com/a/1912682/2029607) for you to have a look at. – XAMlMAX Dec 01 '17 at 08:18
  • 1
    @XAMlMAX, there is no need to apologize. I am very grateful that you are trying to help :) Unfortunately, I can't use TreeView as I have no idea how to style the TreeView to match what I am trying to achieve. I have, however, just successfully implementing MVVM to handle all the problems that I've faced. I'll posted the solution when I'm a bit free. Thanks XAMIMAX for your kind help. – Sam Dec 01 '17 at 09:43

2 Answers2

1

To detect which item gets clicked, you can bind SelectedItem in your ListBox. eg; SelectedItem={Binding Property,Mode=TwoWay} So once you click on the items, In the setter you can raise notify property changed. You can also get the Item index from your Items collection. For that you just need to bind SelectedIndex in your ListBox.

Bicky
  • 41
  • 1
  • 9
  • OMG! That is very clever! Will try that... How about the 2nd problem on the scrolling? – Sam Nov 29 '17 at 11:16
  • I'm not pretty sure why its happening , ideally it should not happen, but you can try ScrollViewer.CanContentScroll="True" and why you are keeping ScrollViewer.VerticalScrollBarVisibility="Disable" ,ScrollViewer.VerticalScrollBarVisibility="Disabled" ,you can set "Auto" instead. For this kind of control, I would prefer to use DataGrid instead of listbox. – Bicky Nov 29 '17 at 11:42
  • I've tried all settings (eg. make it "Auto", etc), but it the scrolling always stop at ListBox. This is because ListBox will consume the scrolling. The only way to stop it is to handle the scroll event, but I am not sure how to handle any event inside the template :( – Sam Nov 29 '17 at 11:53
  • Btw, here is the reference to scrolling problem (which I need to handle "somehow" inside template): https://stackoverflow.com/questions/40625475/wpf-scroll-do-not-working-on-listbox-inside-itemscontrol – Sam Nov 29 '17 at 12:09
  • Without working solution , its difficult to find the actual problems. – Bicky Nov 29 '17 at 12:17
0

For 1 -

Instead of Border, use a button with a control template that defines your image + text.

Create a ICommand property in the View model for each item, then bind it to the listbox item button's Command.

Handle the click in the view-model.

Makubex
  • 1,054
  • 1
  • 9
  • 16