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;
}
}
Actually, I can retrieve the default available Audio Devices using this code, though this code is a Gem, you can use this code in your personal project.
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>
If I run all of my code without any error the it gives the Default audio devices name which is available in the system at that time. As an example, assume that it's give me the Audio device name "Speakers (Conexant SmartAudio HD)".
What I want to do is, I want to give the different Font Size to the Uppercase letters that means in the string Speakers, "S" is capital. I want to give Font Size 12 to this letter "S" and rest of the letter "peakers" should have Font Size 10.
My problem is, this available audio devices names are dynamic that means it's not fix, If I run my WPF Application on multiple computers then it gives different Audio Device names because different computer systems have different available Audio Device, and that is pretty common. Also important point to remember that the Capital letter position are different according to each device with different computer Systems. The main approach should be on how we give different Font Size to the Uppercase letters in a Dynamic String.
I tried so many things like string builder, string replace but nothing works. Any other simple code suggestion is also welcome.
I attach a screenshot on what I try to achieve, actually this things already applied on a various software which are already available in the market. Just look the screenshot You clearly see the uppercase letters are in different Font Size.
CodePudding user response:
You can create a custom TextBlock 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;
}
}
You can use it in your 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 your control template it can be used like below.
<DataTemplate>
<local:CustomTextBlock SnapsToDevicePixels="True" UseLayoutRounding="True" RenderOptions.BitmapScalingMode="HighQuality" RenderOptions.EdgeMode="Aliased" Text="{Binding Name}"/>
</DataTemplate>