question

EmonHaque-1485 avatar image
0 Votes"
EmonHaque-1485 asked EmonHaque-1485 commented

How to align items of ListBoxItem's DataTemplate?

Here in the Balance view everything is aligned properly since I've set fixed width for the columns of Grid inside DataTemplate.
99301-test.gif

and because of that the separator, between the entries under Mr A and his summary, started from far left. I wanted it to start from the left end of the column header, Security, or the maximum number, whichever has the maximum length, in that column. If I give Auto width to Grid columns then it looks like the items in Edit Transaction view. One way I can think of, didn't try that yet, is measure the column header and all numbers in the column in a TextBlock to get the maximum width and set that as the width of numeric columns in the DataTemplate BUT that's complicated. Is there any better way to get columns aligned?

There're two more minor issues in the Balance view. Unlike the previous version of the app where I used ListBox's GroupStyle for grouping as well as summary computation, here I've used an ItemTemplateSelector and the ItemsSource for the ListBox is List<object>. Group header, Mr A, is NameTemplate, entries under Mr A and the single line of Mr B are EntryTemplate and summary of Mr A is SummaryTemplate. Doing summary this way with one level grouing is actually much simpler than implementing that with GroupStyle.

Now, when I hover over the Header, Mr A, and his summary 1) I get the default IsMouseOver effect of ListBoxItem and 2) I can select Header and summary. Wanted to disable the effect and selection capablity by setting Focusable = false in the VisualTree of DataTemplate BUT that doesn;t work!

windows-wpf
test.gif (826.5 KiB)
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

DaisyTian-1203 avatar image
1 Vote"
DaisyTian-1203 answered EmonHaque-1485 commented

I set Focusable = "False" for some Item 1 , Item 4 and IsHitTestVisible="False" for Item 5 as below:

    <ListBox Name="lb" Width="100" Height="155" SelectionMode="Single" HorizontalContentAlignment="Left">
             <ListBoxItem Focusable="False">Item 1</ListBoxItem>
             <ListBoxItem >Item 2</ListBoxItem>
             <ListBoxItem >Item 3</ListBoxItem>
             <ListBoxItem Focusable="False">Item 4 </ListBoxItem>
             <ListBoxItem IsHitTestVisible="False">Item 5</ListBoxItem>
             <ListBoxItem>Item 6555</ListBoxItem>
             <ListBoxItem>Item 7444</ListBoxItem>
             <ListBoxItem>Item 8333</ListBoxItem>
             <ListBoxItem>Item 9777</ListBoxItem>
             <ListBoxItem>Item 107777</ListBoxItem>
         </ListBox>

Run it and get:
99387-3.gif

The Item 5 can't get focused when MouseOver, so could you use IsHitTestVisible="False" to replace Focusable = false in the VisualTree to implement what you want for ListBoxItem, by the way , did you set HorizontalContentAlignment="Left" for the ListBox to align the ListBoxItem Content ?


If the response is helpful, please click "Accept Answer" and upvote it.
Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


3.gif (44.5 KiB)
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@DaisyZhou-MSFT, response in answer section.

0 Votes 0 ·

@DaisyTian-MSFT, yes IsHitTestVisible="False" works BUT I'd to extend the ListBox and ListBoxItem. Posted the solution in answer section.

0 Votes 0 ·
EmonHaque-1485 avatar image
0 Votes"
EmonHaque-1485 answered EmonHaque-1485 edited

@DaisyTian-MSFT, HorizontalContentAlignment="Stretch" in the ListBox, IsHitTestVisible in the VisualTree doesn't work! For example, for the NameTemplate:

 class BalanceNameTemplate : DataTemplate
 {
     public BalanceNameTemplate(bool isDividerBlue = true) {
         var border = new FrameworkElementFactory(typeof(Border));
         var name = new FrameworkElementFactory(typeof(TextBlock));
         if (isDividerBlue) {
             border.SetValue(Border.BorderThicknessProperty, new Thickness(0, 0, 0, 1));
             border.SetValue(Border.BorderBrushProperty, Brushes.LightBlue);
         }
         else {
             border.SetValue(Border.BorderThicknessProperty, new Thickness(0, 0, 0, 0.5));
             border.SetValue(Border.BorderBrushProperty, Brushes.Black);
         }
         border.SetValue(Border.FocusableProperty, false);
         name.SetValue(TextBlock.FontWeightProperty, FontWeights.Bold);
         name.SetBinding(TextBlock.TextProperty, new Binding());
         border.AppendChild(name);
         border.SetValue(Border.IsHitTestVisibleProperty, false);
         VisualTree = border;
     }
 }

and here's the ItemTemplateSelector for the ListBox:

 class BalanceTemplateSelector : DataTemplateSelector
 {
     BalanceNameTemplate name;
     BalanceEntryTemplate balance;
     BalanceSummaryTemplate summary;
     public BalanceTemplateSelector(bool isDividerBlue = true) {
         name = new BalanceNameTemplate(isDividerBlue);
         balance = new BalanceEntryTemplate(isDividerBlue? 100 : 70);
         summary = new BalanceSummaryTemplate(isDividerBlue);
     }
     public override DataTemplate SelectTemplate(object item, DependencyObject container) {
         if (item is string) return name;
         if (item is Balance) return balance;
         return summary;
     }
 }

those 100 and 70s are the fixed width for columns, when presented in the app, it takes 100 and for actual printing on page it's 70.

I've been trying with the Grid.SetIsSharedSizeScope(this, true); for the alignment. Here this is the CustomControl, FrameworkElement, it works BUT there's no spacing in between grid columns.

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

EmonHaque-1485 avatar image
0 Votes"
EmonHaque-1485 answered

To disable the selection of header and footer items in ListBox, I, now, have this extended ListBox:

 class BalanceListBox : ListBox
 {
     object item;
     bool isForPrinting;
     public BalanceListBox(bool isForPrinting = false) {
         this.isForPrinting = isForPrinting;
         HorizontalContentAlignment = HorizontalAlignment.Stretch;
         BorderThickness = new Thickness(0);
         Resources.Add(typeof(ScrollViewer), new Style() {
             Setters = {
                 new Setter(ScrollViewer.HorizontalScrollBarVisibilityProperty, ScrollBarVisibility.Disabled),
                 new Setter(ScrollViewer.TemplateProperty, new LedgerScrollTemplate())
             }
         });
     }
     protected override bool IsItemItsOwnContainerOverride(object item) {
         this.item = item;
         return false;
     }
     protected override DependencyObject GetContainerForItemOverride() {
         if (item is string) return new HeaderItem((string)item, isForPrinting);
         if (item is Balance) return new EntryItem((Balance)item, isForPrinting);
         return new FooterItem((Tuple<int, int, int>)item, isForPrinting);
     }
 }
 class HeaderItem : ListBoxItem
 {
     public HeaderItem(string item, bool isForPrinting) {
         IsHitTestVisible = false;
         double thickness = 1;
         if (isForPrinting) {
             Padding = new Thickness(0);
             thickness = 0.5;
         }
         Content = new Border() {
             BorderThickness = new Thickness(0, 0, 0, thickness),
             BorderBrush = isForPrinting ? Brushes.Black : Brushes.LightBlue,
             Child = new TextBlock() {
                 FontWeight = FontWeights.Bold,
                 Text = item
             }
         };
     }
 }
 class EntryItem : ListBoxItem
 {
     public EntryItem(Balance item, bool isForPrinting) {
         FocusVisualStyle = null;
         if (isForPrinting) Padding = new Thickness(0);
         var name = new TextBlock() { HorizontalAlignment = HorizontalAlignment.Left };
         if(item.Count > 1) {
             var space = new Run() { Text = item.Space };
             name.Inlines.Add(space);
             name.Margin = new Thickness(10, 0, 0, 0);
         }
         else {
             var tenant = new Run() { Text = item.Tenant, FontWeight = FontWeights.Bold };
             name.Inlines.Add(tenant);
             if(item.Space != null) {
                 var space = new Run() { Text = " - " + item.Space };
                 name.Inlines.Add(space);
             }
         }
         if (item.IsExpired) {
             var expDate = new Run() {
                 Text = $" (expired on {item.DateEnd.Value.ToString("dd MMMM yyyy") })",
                 FontStyle = FontStyles.Italic
             };
             name.Inlines.Add(expDate);
         }
         var date = new TextBlock() { Text = item.DateStart.Value.ToString("dd MMMM yyyy"), HorizontalAlignment = HorizontalAlignment.Center };
         var security = new TextBlock() { Text = item.Security.ToString(Constants.NumberFormat), HorizontalAlignment = HorizontalAlignment.Right };
         var rent = new TextBlock() { Text = item.Rent.ToString(Constants.NumberFormat), HorizontalAlignment = HorizontalAlignment.Right };
         var due = new TextBlock() { Text = item.Due.ToString(Constants.NumberFormat), HorizontalAlignment = HorizontalAlignment.Right };
         Grid.SetColumn(date, 1);
         Grid.SetColumn(security, 2);
         Grid.SetColumn(rent, 3);
         Grid.SetColumn(due, 4);
         var width = isForPrinting ? 70 : 100;
         Content = new Grid() {
             ColumnDefinitions = {
                 new ColumnDefinition(),
                 new ColumnDefinition(){ Width = new GridLength(150) },
                 new ColumnDefinition(){ Width = new GridLength(width) },
                 new ColumnDefinition(){ Width = new GridLength(width) },
                 new ColumnDefinition(){ Width = new GridLength(width) }
             },
             Children = { name, date, security, rent, due }
         };
     }
 }
 class FooterItem : ListBoxItem
 { 
     public FooterItem(Tuple<int, int, int> values, bool isForPrinting) {
         IsHitTestVisible = false;
         if (isForPrinting) {
             Padding = new Thickness(0);
         }
         var separator = new Separator() { Background = isForPrinting ? Brushes.Black : Brushes.LightBlue };
         var security = new TextBlock() { Text = values.Item1.ToString(Constants.NumberFormat) };
         var rent = new TextBlock() { Text = values.Item2.ToString(Constants.NumberFormat) };
         var due = new TextBlock() { Text = values.Item3.ToString(Constants.NumberFormat) };
         Grid.SetColumn(security, 1);
         Grid.SetColumn(rent, 2);
         Grid.SetColumn(due, 3);
         Grid.SetColumn(separator, 1);
         Grid.SetColumnSpan(separator, 3);
         var width = isForPrinting ? 70 : 100;
         Content = new Grid() {
             ColumnDefinitions = {
                 new ColumnDefinition(),
                 new ColumnDefinition(){ Width = new GridLength(width) },
                 new ColumnDefinition(){ Width = new GridLength(width) },
                 new ColumnDefinition(){ Width = new GridLength(width) }
             },
             RowDefinitions = {
                 new RowDefinition(){ Height = GridLength.Auto },
                 new RowDefinition()
             },
             Resources = {
                 {
                     typeof(TextBlock),
                     new Style() {
                         Setters = {
                             new Setter(TextBlock.FontWeightProperty, FontWeights.Bold),
                             new Setter(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Right),
                             new Setter(Grid.RowProperty, 1)
                         }
                     }
                 }
             },
             Children = {separator, security, rent, due }
         };
     }
 }

IsHitTestVisible = false for Header and Footer item. With this, I don't need any DataTemplate or ItemTemplateSelector and it works:

103953-test.gif

If I want to have a filter on the ListBox the usual way with DataTemplate is better, otherwise Filter function will be complicated with this approach. One more thing. I've set this.item = item; in IsItemItsOwnContainerOverride. Can I get rid of that and get the item reference in GetContainerForItemOverride somehow?


test.gif (242.6 KiB)
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.