How Can I give Different Font size to the Uppercase characters of a string that binded to A List in WPF?

MERUN KUMAR MAITY 511 Reputation points
2022-10-06T10:44:50.947+00:00

Hi there, I have a WPF application where I use a ComboBox. My ComboBox ItemSource is bind to a List <> which I populate from C# in background.

Here is the C# code of that List<> :

   // Main code  
                 List<AudioDevice> devices = new List<AudioDevice>();  
             HRESULT hr = HRESULT.E_FAIL;  
             Guid CLSID_MMDeviceEnumerator = new Guid("{BCDE0395-E52F-467C-8E3D-C4579291692E}");  
             Type MMDeviceEnumeratorType = Type.GetTypeFromCLSID(CLSID_MMDeviceEnumerator, true);  
             object MMDeviceEnumerator = Activator.CreateInstance(MMDeviceEnumeratorType);  
             IMMDeviceEnumerator pMMDeviceEnumerator = (IMMDeviceEnumerator)MMDeviceEnumerator;  
             if (pMMDeviceEnumerator != null)  
             {  
                 string sIdDefaultRender = null;  
                 string sIdDefaultCapture = null;  
                 IMMDevice pDefaultDevice = null;  
                 hr = pMMDeviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eConsole, out pDefaultDevice);  
                 if (hr == HRESULT.S_OK)  
                 {  
                     IntPtr hGlobal = Marshal.AllocHGlobal(260);  
                     hr = pDefaultDevice.GetId(out hGlobal);  
                     sIdDefaultRender = Marshal.PtrToStringUni(hGlobal);  
                     Marshal.FreeHGlobal(hGlobal);  
                     Marshal.ReleaseComObject(pDefaultDevice);  
                 }  
                 hr = pMMDeviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eCapture, ERole.eConsole, out pDefaultDevice);  
                 if (hr == HRESULT.S_OK)  
                 {  
                     IntPtr hGlobal = Marshal.AllocHGlobal(260);  
                     hr = pDefaultDevice.GetId(out hGlobal);  
                     sIdDefaultCapture = Marshal.PtrToStringUni(hGlobal);  
                     Marshal.FreeHGlobal(hGlobal);  
                     Marshal.ReleaseComObject(pDefaultDevice);  
                 }  
                 IMMDeviceCollection pDeviceCollection = null;  
                 hr = pMMDeviceEnumerator.EnumAudioEndpoints(EDataFlow.eAll, DEVICE_STATE_ACTIVE | DEVICE_STATE_UNPLUGGED, out pDeviceCollection);  
                 if (hr == HRESULT.S_OK)  
                 {  
                     uint nDevices = 0;  
                     hr = pDeviceCollection.GetCount(out nDevices);  
                     devices.Add(new AudioDevice() { Name = "System default", Direction = "Playback", Id = sIdDefaultRender, Default = true });  
                     for (uint i = 0; i < nDevices; i++)  
                     {  
                         IMMDevice pDevice = null;  
                         hr = pDeviceCollection.Item(i, out pDevice);  
                         if (hr == HRESULT.S_OK)  
                         {  
                             IPropertyStore pPropertyStore = null;  
                             hr = pDevice.OpenPropertyStore(STGM_READ, out pPropertyStore);  
                             if (hr == HRESULT.S_OK)  
                             {  
                                 string sFriendlyName = null;  
                                 string sDesc = null;  
                                 PROPVARIANT pv = new PROPVARIANT();  
                                 hr = pPropertyStore.GetValue(ref PKEY_Device_FriendlyName, out pv);  
                                 if (hr == HRESULT.S_OK)  
                                 {  
                                     sFriendlyName = Marshal.PtrToStringUni(pv.pwszVal);  
                                 }  
                                 hr = pPropertyStore.GetValue(ref PKEY_Device_DeviceDesc, out pv);  
                                 if (hr == HRESULT.S_OK)  
                                 {  
                                     sDesc = Marshal.PtrToStringUni(pv.pwszVal);  
                                 }  
                                 IntPtr hGlobal = Marshal.AllocHGlobal(260);  
                                 hr = pDevice.GetId(out hGlobal);  
                                 string sId = Marshal.PtrToStringUni(hGlobal);  
                                 Marshal.FreeHGlobal(hGlobal);  
                                 IMMEndpoint pEndpoint = null;  
                                 pEndpoint = (IMMEndpoint)pDevice;  
                                 EDataFlow eDirection = EDataFlow.eAll;  
                                 hr = pEndpoint.GetDataFlow(out eDirection);  
                                 //System.Diagnostics.Trace.WriteLine("\tDirection : " + eDirection.ToString());  
                                 string sDirection = "";  
                                 if (eDirection == EDataFlow.eRender)  
                                     sDirection = "Playback";  
                                 else if (eDirection == EDataFlow.eCapture)  
                                     sDirection = "Recording";  
                                 int nState = 0;  
                                 hr = pDevice.GetState(out nState);  
                                 if ((nState == DEVICE_STATE_ACTIVE))  
                                 {  
                                     devices.Add(new AudioDevice() { Name = sFriendlyName, Direction = sDirection, Id = sId, Default = (sId == sIdDefaultRender || (sId == sIdDefaultCapture)) });  
                                     //sFriendlyName += (sId == sIdDefaultRender || sId == sIdDefaultCapture) ? " (System default)" : "";  
                                     //devices.Add(new AudioDevice() { Name = sFriendlyName, Direction = sDirection, Default = (sId == sIdDefaultRender || (sId == sIdDefaultCapture)) });  
                                     ////devices.Add(new AudioDevice() { Name = sFriendlyName, Direction = sDirection, Default = (sId == sIdDefaultRender || (sId == sIdDefaultCapture)) });  
                                 }  
                                 Marshal.ReleaseComObject(pPropertyStore);  
                             }  
                             Marshal.ReleaseComObject(pDevice);  
                         }  
                     }  
                     devices.Insert(devices.Count - 0, new AudioDevice() { Name = "Selected application ...", Direction = "Recording", Id = "Idlast", Default = false });  
                 }  
                 Marshal.ReleaseComObject(pDeviceCollection);  
                 ListCollectionView lcv = new ListCollectionView(devices);  
                 lcv.GroupDescriptions.Add(new PropertyGroupDescription("Direction"));  
                 this.cmb1.ItemsSource = lcv;  
             }  
         }  
         public class AudioDevice  
                 {  
                     public string Name { get; set; }  
                     public string Direction { get; set; }  
                     public string Id { get; set; }  
                     public bool Default { get; set; }  
      
         public override string ToString()  
                    {  
                        return Name;  
                    }  
      
                 }  

If you carefully see the code then You see this line this.cmb1.ItemsSource = lcv; That means the List<> is added as ItemSource of the ComboBox, Now, I have a control template for comboBoxItem, my ComboBoxitem control template have lots of visual customisation and effects according to my choice.

Here is my ComboBoxItem controltemplate :

<Style x:Key="{x:Type ComboBoxItem}" TargetType="{x:Type ComboBoxItem}">  
            <Setter Property="SnapsToDevicePixels" Value="True"/>  
            <Setter Property="UseLayoutRounding" Value="True"/>  
            <Setter Property="RenderOptions.BitmapScalingMode" Value="NearestNeighbor"/>  
            <Setter Property="RenderOptions.ClearTypeHint" Value="Enabled"/>  
            <Setter Property="FontFamily" Value="Segoe UI"/>  
            <Setter Property="FontSize" Value="9"/>  
            <Setter Property="TextOptions.TextFormattingMode" Value="Display"/>  
            <Setter Property="Foreground" Value="#939393"/>  
            <Setter Property="Template">  
                <Setter.Value>  
                    <ControlTemplate TargetType="{x:Type ComboBoxItem}">  
                        <Grid>  
                            <Border x:Name="gd" Background="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}},Path=Background}" BorderThickness="0" SnapsToDevicePixels="True" UseLayoutRounding="True" RenderOptions.BitmapScalingMode="NearestNeighbor" RenderOptions.ClearTypeHint="Enabled">  
                                <ContentPresenter  
                                Name="ContentSite" Margin="25, 3, 0, 11" VerticalAlignment="Center">  
                                    <ContentPresenter.ContentTemplate>  
                                        <DataTemplate>  
                                            <TextBlock SnapsToDevicePixels="True" UseLayoutRounding="True" RenderOptions.BitmapScalingMode="HighQuality" RenderOptions.EdgeMode="Aliased">                                             
                                                <Run Text="{Binding Name}"/>  
                                            </TextBlock>  
                                        </DataTemplate>  
                                    </ContentPresenter.ContentTemplate>  
                                </ContentPresenter>  
                            </Border>  
                            <Rectangle x:Name="Border1" Width="12.2" Height="11" Margin="-220,-7,0,0"          RenderOptions.BitmapScalingMode="HighQuality" RenderOptions.ClearTypeHint="Auto" SnapsToDevicePixels="True" RenderOptions.EdgeMode="Unspecified">  
                                <Rectangle.Fill>  
                                    <DrawingBrush>  
                                        <DrawingBrush.Drawing>  
                                            <DrawingGroup x:Name="DrawingLayer">  
                                                <DrawingGroup.ClipGeometry>  
                                                    <RectangleGeometry Rect="0,0,512,512" />  
                                                </DrawingGroup.ClipGeometry>  
                                                <DrawingGroup Transform="0.1,0,0,-0.1,0,512">  
                                                    <GeometryDrawing Brush="#d0021b">  
                                                        <GeometryDrawing.Geometry>  
                                                            <PathGeometry FillRule="Nonzero" Figures="M4670,3950L2560,1350 1560,2570 830,2190 2590,170 5140,3260 4670,3950z" />  
                                                        </GeometryDrawing.Geometry>  
                                                        <GeometryDrawing.Pen>  
                                                            <Pen Brush="#d0021b" Thickness="0" StartLineCap="Flat" EndLineCap="Flat" LineJoin="Miter" />  
                                                        </GeometryDrawing.Pen>  
                                                    </GeometryDrawing>  
                                                </DrawingGroup>  
                                            </DrawingGroup>  
                                        </DrawingBrush.Drawing>  
                                    </DrawingBrush>  
                                </Rectangle.Fill>  
                            </Rectangle>  
                        </Grid>  
                        <ControlTemplate.Triggers>  
                            <Trigger Property="ComboBoxItem.IsMouseOver" Value="True">  
                                <Setter TargetName="gd" Property="Background" Value="#3c3c3c"></Setter>  
                                <Setter TargetName="gd" Property="TextElement.Foreground" Value="#ffffff"></Setter>  
                                <Setter TargetName="gd" Property="SnapsToDevicePixels" Value="True"/>  
                                <Setter TargetName="gd" Property="UseLayoutRounding" Value="True"/>  
                                <Setter TargetName="gd" Property="RenderOptions.BitmapScalingMode" Value="NearestNeighbor"/>  
                                <Setter TargetName="gd" Property="RenderOptions.ClearTypeHint" Value="Enabled"/>  
                                <Setter TargetName="Border1" Property="Visibility" Value="Collapsed"/>  
                            </Trigger>  
                            <Trigger Property="ComboBoxItem.IsMouseOver" Value="False">  
                                <Setter TargetName="Border1" Property="Visibility" Value="Hidden"/>  
                            </Trigger>  
                            <Trigger Property="ComboBoxItem.IsSelected" Value="True">  
                                <Setter TargetName="Border1" Property="Visibility" Value="Visible"/>  
                            </Trigger>  
                        </ControlTemplate.Triggers>  
                    </ControlTemplate>  
                </Setter.Value>  
            </Setter>  
        </Style>  
  

After adding this controltemplate in my ComboBoxitem, my comboBox looks amazing. My ComboBox content is added from a List<> not from a ComboBox item. If you carefully see this controltemplate, you can see this line <Run Text="{Binding Name}"/> , this is How I bind the List<> into my ComboBoxitem controltemplate.

All of my code run perfectly But my Main question is How Can I give different Font Size to the Uppercase characters and Lowercase characters of that string (The string is the Name which I get from the Class Audio device).

My approach is to separate Uppercase and Lowercase characters from that string "Name" using a IValueConverter. And then give bigger Font Size to the Uppercase converter and lower Font Size to the Lowercase converter.

Something like this :

 <DataTemplate>  
                                                     <TextBlock SnapsToDevicePixels="True" UseLayoutRounding="True" RenderOptions.BitmapScalingMode="HighQuality" RenderOptions.EdgeMode="Aliased">  
                                                         <Run x:Name="first" FontSize="18" Text="{Binding Name,Converter={StaticResource UppercaseConverter }}" />  
                                                         <Run x:Name="rest" FontSize="10" Text="{Binding Name, Converter={StaticResource LowercaseConverter}}" />  
                                                     </TextBlock>  
                                                 </DataTemplate>  

I tried so many things like string builder, string replace but nothing works. Any other simple code suggestion is also welcome.

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,670 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,234 questions
0 comments No comments
{count} votes

3 answers

Sort by: Most helpful
  1. AgaveJoe 26,191 Reputation points
    2022-10-06T13:49:40.393+00:00

    The strings are not fix length and the caps are in different positions so you'll need generate dynamic markup using C# not XAML. Do a Google search for dynamic inline style in WPF. However, I'm not sure if it's possible for a combobox to have an inline style. This is not possible in the web markup world. On web we have to use code (JavaScript) to create a custom component. I guess you'll need to do some research.
    Let us know what you find.

    1 person found this answer helpful.

  2. Hui Liu-MSFT 38,191 Reputation points Microsoft Vendor
    2022-10-11T02:41:31.817+00:00

    Hi,@MERUN KUMAR MAITY . AgaveJoe's answer is great. Based on my research, it is difficult to return a specific font size for characters in variable length and variable content strings bound to controls.
    A string does not have a font. If you are displaying a string as part of a control (such as a ComboBox) then how you format it depends on the functionality available within that control. If you are drawing that string as text on a display or in a printout, then you control the font that is used as part of the drawing commands.

    ----------------------------------------------------------------------------

    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. MERUN KUMAR MAITY 511 Reputation points
    2022-10-11T19:43:49.967+00:00

    I can create a custom Text Block and override its text property.

    using System.Windows;  
    using System.Windows.Controls;  
    using System.Windows.Documents;  
      
    namespace WpfApp1;  
      
    public class CustomTextBlock : TextBlock  
    {  
        public new string Text  
        {  
            get => (string)GetValue(TextProperty);  
            set => SetValue(TextProperty, value);  
        }  
      
        // Using a DependencyProperty as the backing store for Text.  This enables animation, styling, binding, etc...  
        public static new readonly DependencyProperty TextProperty =  
            DependencyProperty.Register("Text", typeof(string), typeof(CustomTextBlock), new PropertyMetadata(null, (d, ea) =>  
            {  
                if (d is TextBlock textBlock)  
                {  
                    textBlock.Inlines.Clear();  
      
                    if (ea.NewValue is string value)  
                    {  
                        foreach (char c in value)  
                        {  
                            textBlock.Inlines.Add(new Run { FontSize = GetFontSize(c), Text = c.ToString() });  
                        }  
                    }  
                }  
            }));  
      
        private static double GetFontSize(char c)  
        {  
            // You can add your other logic here  
            /*if (c == '#')  
            {  
                return 5;  
            }  
      
            if (char.IsNumber(c))  
            {  
                return 22;  
            }*/  
      
            if (char.IsUpper(c))  
            {  
                return 18;  
            }  
      
            return 10;  
        }  
    }  
    

    I can use it in my XAML this way. I used the CustomTextBlock in the ComboBox ItemTemplate but I think it should also work in the ComboBoxItem ControlTemplate.

    <Window x:Class="WpfApp1.MainWindow"  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
        xmlns:local="clr-namespace:WpfApp1"  
        xmlns:sys="clr-namespace:System;assembly=mscorlib"  
        mc:Ignorable="d"  
        Title="MainWindow" Height="450" Width="800">  
        <Grid>  
            <ComboBox VerticalAlignment="Top">  
                <ComboBox.ItemsSource>  
                    <x:Array Type="sys:String">  
                        <sys:String>Some Item #0</sys:String>  
                        <sys:String>Some Item #1</sys:String>  
                        <sys:String>Some Item #2</sys:String>  
                    </x:Array>  
                </ComboBox.ItemsSource>  
                <ComboBox.ItemTemplate>  
                    <DataTemplate>  
                        <local:CustomTextBlock Text="{Binding}"/>  
                    </DataTemplate>  
                </ComboBox.ItemTemplate>  
            </ComboBox>  
        </Grid>  
    </Window>  
    

    In my ComboBoxItem ControlTemplate it can be used like below.

    <ContentPresenter  
                                    Name="ContentSite" Margin="25, 6.5, 0, 6.5" VerticalAlignment="Center">  
                                        <ContentPresenter.ContentTemplate>  
                                            <DataTemplate>  
                                                <local:CustomTextBlock Text="{Binding Name}" SnapsToDevicePixels="True" UseLayoutRounding="True" RenderOptions.BitmapScalingMode="HighQuality" RenderOptions.EdgeMode="Aliased"   />  
                                            </DataTemplate>  
                                        </ContentPresenter.ContentTemplate>  
                                    </ContentPresenter>  
    

    This is a part of my ComboBoxItem ControlTemplate code where I do the Binding with Name and apply the CustomTextBlock.

    0 comments No comments