0

I have a powershell script that displays as a WPF form to allow users to control printers etc (map / unmap / set default), everything on it is working nicely, bit I have an issue with trying to hide a printer (from a work around where understandable listview hate having just 1 item bound to them)

So if the user has 1 mapped printer I also add in the "Send to OneNote 16" printer into the object that is bound to the list view, but to avoid confusion I'd ideally like to hide that printer from the list.

XAML Code section

<ListBox x:Name="MyListbox" HorizontalContentAlignment="Stretch">
      <ListBox.ItemContainerStyle>
        <Style TargetType="{x:Type ListBoxItem}">
        <Setter Property="ListBoxItem.Visibility" Value="Visible"/>
          <Style.Triggers>
            <DataTrigger Binding="{Binding Path=Name}" Value="Send to OneNote 16">
              <Setter Property="ListBoxItem.Visibility" Value="Collapsed"/>
            </DataTrigger>
          </Style.Triggers>
        </Style>
      </ListBox.ItemContainerStyle>
      <ListBox.ItemTemplate>
        <DataTemplate>
          <Border CornerRadius="6" BorderBrush="LightGray" Background="White" BorderThickness="1" Padding="8">
              <StackPanel>
                <DockPanel>
                  <TextBlock Text="Name: " FontWeight="Bold" Foreground="LimeGreen" />
                  <TextBlock>
                      <TextBlock FontWeight="Bold" Text="{Binding Path=Name}" />
                  </TextBlock>
                </DockPanel>
                <DockPanel>
                  <TextBlock Text="Server: " />
                  <TextBlock Foreground="Black">
                      <TextBlock Text="$($PrintServerName)" />
                  </TextBlock>
                  <TextBlock Text=" | Job Count: " />
                  <TextBlock Foreground="Black">
                      <TextBlock Text="{Binding Path=JobCount}" />
                  </TextBlock>
                </DockPanel>
              </StackPanel>
            </Border>
          </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>

Powershell bit that populates the object that is bound to MyListBox

    function Get-MappedPrinterInfo {

  BEGIN { 

    # Setup variables, and get mapped printer information from registry
    [object]$Printers = Get-ChildItem Registry::\HKEY_CURRENT_USER\Printers\Connections
    [object]$Output = New-Object System.Collections.ObjectModel.ObservableCollection[Object]
  }

  PROCESS {

    foreach ($Printer in $Printers) {

      # Extract printer name
      [string]$PrinterName = $Printer.Name

      # Correct Case
      $PrinterName = $PrinterName.ToLower()

      # Cleanup data
      $PrinterName = $PrinterName.replace("printerserver.mynetwork.local", "")
      $PrinterName = $PrinterName.replace("printerserver", "")
      $PrinterName = $PrinterName.replace("hkey_current_user\printers\connections\", "")
      $PrinterName = $PrinterName.replace(",", "")

      # Get additional data on printer from its print server
      $Output += Get-Printer -ComputerName $PrintServerName | Where-Object Shared -eq $true | Where-Object Name -eq $PrinterName
    }

    # Check object length, and pad out if required.
    if ($Output.Count -eq 1) {
      $Output += Get-Printer | Where-Object Name -eq "Send to OneNote 16"
    }
  }

  END {

    # Return data
    return $Output
  }
}

# Populate my mapped printer list.
$MyListbox.ItemsSource = Get-MappedPrinterInfo

The style section all works a treat, but the datatrigger never fires, if I set the Setter property (4th line in XAML section) then I can hide/ unhide all items, but Ideally I want to be able to hide by name.

Thanks in advance.

3
  • Seems to me that you're trying to fix from the wrong direction - why is it a problem if the listview only has one item? Commented Jan 10, 2020 at 9:19
  • If you send 1 item to a listview it doesn't show anything, so I figured easiest way would be to send 2, then hide the one I don;t want to show, giving the impression its all working Commented Jan 10, 2020 at 9:23
  • See below - you;re working around a problem the wrong way :) Commented Jan 10, 2020 at 10:54

1 Answer 1

2

There is no need for your add-an-extra-item-then-hide-it workaround:

The root of your problem is that outputting a collection from a PowerShell function causes its enumeration by default, which in the case of a single-item collection means that that item itself is output, not an array of items ([object[]], which is how PowerShell implicitly collects multiple outputs from a command when you assign to an l-value).

@(), the array-subexpression operator, exists precisely to address this scenario:

You wrap it around a command whose output you always want to treat as an array, even if the command happens to output a single object:

# Populate my mapped printer list.
# @(...) ensures that the function output is an array.
$MyListbox.ItemsSource = @(Get-MappedPrinterInfo)

Note that it is possible to solve this problem from inside your function - by wrapping your output collection in an aux. single-item array, so that the original collection is received by the caller - but it's generally better not to write such functions, as they will exhibit nonstandard behavior when used in a pipeline, where other commands expect item-by-item input, not entire collections.

If you want to do it nonetheless (which is fine for private, special-purpose functions):

...
END {
    # Return data wrapped in a single-item aux. array to ensure 
    # that the $Output collection is returned as a whole.
    return , $Output
}

A few asides regarding your code:

[object]$Printers = ...

An [object] cast is pointless in PowerShell, and casts generally don't work the same way as in C#, for instance.

Whatever type the RHS has becomes the effective type of $Printers.

If you use a specific type to constrain the LHS, however, conversion to that type is attempted, whenever you assign a value to that variable.


$Output += ...

+= quietly converts $Output to a (new instance of an) array ([object[]]), irrespective of the original type, which isn't your intent and is also inefficient.

You need to call the .Add() method on the collection instance originally stored in $Output instead.

Sign up to request clarification or add additional context in comments.

2 Comments

The object thing makes sense I was treating it like C#'s smaller brother, the .Add() method works a treat, it didn't like having Get-Printer put in the brackets, put output that to a seperate variable then drop that in like: Add($PrinterDetails) and it all works an absolte treat, thank you very much. On the datatrigger thing is that just an oddity with binding with using powershell or is there something fundamentally wrong there as well (not that its needed now I'm using the objects properly).
@Liquidkristal You could also do .Add((Get-Printer ...)) directly. Unfortunately, since I'm not familiar with WPF, I don't know the answer to why the DataTrigger thing didn't work for you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.