I have a wpf app, c# that displays texts (teahcer's name) when a combobox is closed. Basically it is contained in 1 string and being passed to the classInstance.TeachersComboBoxText or Text="{Binding Path=TeachersComboBoxText}" . Everything is great but the client is requesting for the text to be in italic if the teacher is flagged as not default or secondary teacher. Basically, I need to have a string but different font styles is applied to it,depends on my data.
<ComboBox Name="instanceTeachersComboBox"
IsEditable="True"
IsReadOnly="True"
ItemsSource="{Binding Path=TeacherList}" DropDownClosed="teachersComboBox_DropDownClosed"
Text="{Binding Path=TeachersComboBoxText}"
FontWeight="{Binding Path=IsSelectedFontWeight}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
Background="{Binding Path=IsActiveColour}"
Content="{Binding Path=Display}"
Foreground="{Binding Path=IsClashColour}"
FontStyle="{Binding Path=EndDateFontStyle}"/>
<Image Source="/SchoolEdgeTimetable;component/Images/greentick16x16.png"
Margin="5,0,0,0"
Visibility="{Binding Path=IsSpecialitySubjectVisibility}"></Image>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
=== this is the code ====
if (classInstance.TeacherList.Any(c => c.IsSelected == true && c.IsDefault == false) || instanceCount != defaultCount)
{
System.Windows.Forms.RichTextBox rtf = new System.Windows.Forms.RichTextBox();
foreach (var item in data)
{
if (!item.IsDefault)
{
rtf.SelectionFont = new Font("Arial", 12, System.Drawing.FontStyle.Italic);
rtf.AppendText(item.Display);
}
text = GT.Join2Strings(text, item.IsDefault? item.Display : rtf.Text, "\n");
}
}
else
text = DEFAULT_TEXT;
classInstance.TeachersComboBoxText = text;
I tried rtf because it seems that I can't apply it in plain string, but the code is not working. Is there any way to solve this without using rtf? or if not why is my code not working? anyone ca suggest a better way to do this will be appreciated. thanks
UPDATE:
People are suggesting to modify the template which I already did, that is working great in showing the items when a combobox is open. One of my items is RED and in italic as you can see BUT What I am trying to do though is modify display of the combobox TEXT property. I have attached an image to get a better view on what I mean.
thanks
It's pretty simple if you know how to utilise <Run> tag of TextBlock control in code behind. See below code:
//loop through all the teachers
foreach (var teacher in vm.Teachers)
{
//check if teacher is default
if (teacher.IsDefault)
{
//add text to "tb" textblock, in italic style
tb.Inlines.Add(new Run(teacher.Name) { FontStyle = FontStyles.Italic });
}
else
{
//add text to "tb" textblock
tb.Inlines.Add(new Run(teacher.Name));
}
}
If you are keen to know more about inline formatting, here is a cool blog post for same. Let me know if you need any more help on this.
You could handle the Loaded event for the CheckBox in the ItemTemplate and set its Content property to a TextBlock of Inline elements, e.g.:
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
Background="{Binding Path=IsActiveColour}"
Foreground="{Binding Path=IsClashColour}"
FontStyle="{Binding Path=EndDateFontStyle}"
Loaded="CheckBox_Loaded">
</CheckBox>
<Image Source="/SchoolEdgeTimetable;component/Images/greentick16x16.png"
Margin="5,0,0,0"
Visibility="{Binding Path=IsSpecialitySubjectVisibility}"></Image>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
private void CheckBox_Loaded(object sender, RoutedEventArgs e)
{
CheckBox checkBox = sender as CheckBox;
dynamic dataObject = checkBox.DataContext;
string display = dataObject.Display;
TextBlock textBlock = new TextBlock();
//split the display string and add Inlines to the TextBlock as suggested by #Naresh Ravlani here...
checkBox.Content = textBlock;
}
Add a datatrigger that changes your font to Italic:
<DataTrigger Binding="{Binding IsDefault}" Value="1">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
Related
I have an ItemsControl which is bound to a list:
<ItemsControl x:Name="icFiles" ItemsSource="{Binding Path=files}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
<TextBlock x:Name="ThisTextBlock" Text="{Binding FileName}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
private readonly List<FileModel> files = new();
icFiles.ItemsSource = files;
I want to highlight certain text in the TextBlock in the ItemsControl. For this, I thought about using a TextPointer:
string? highlightText = "blue";
int highlightTextIndex = ThisTextBlock.Text.IndexOf(highlightText);
if(highlightTextIndex >= 0)
{
TextPointer textStartPointer = ThisTextBlock.ContentStart.DocumentStart.GetInsertionPosition(LogicalDirection.Forward);
TextRange? highlightTextRange = new TextRange(textStartPointer.GetPositionAtOffset(highlightTextIndex), textStartPointer.GetPositionAtOffset(highlightTextIndex + highlightText.Length));
highlightTextRange.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Blue);
}
}
How do I find this ThisTextBlock?
You need to access the item container's content template (which is the item's DataTemplate).
In case of the ItemsControl, you can use the following example to obtain a named element from the DataTemplate:
for (int itemIndex = 0; itemIndex < this.ItemsControl.Items.Count; itemIndex++)
{
var itemContainer = this.ItemsControl.ItemContainerGenerator.ContainerFromIndex(itemIndex) as ContentPresenter;
var textBlock = itemContainer.ContentTemplate.FindName("ThisTextBlock", itemContainer) as TextBlock;
HighlightText(textBlock);
}
A simple implementation that searches an element in the visual tree can be found at How to: Microsoft Docs: How to: Find DataTemplate-Generated Elements. You can copy and use the example's helper method FindVisualChild to search for elements by type rather than by name. The method is part of an example that shows how to get the content of the DataTemplate in case you use a ListBox or ListView.
In case you didn't modified the ListBoxItem template or don't expect it to change, you can use this simplified and faster version (to find named elements):
for (int itemIndex = 0; itemIndex < this.ListBox.Items.Count; itemIndex++)
{
var listBoxItemContainer = this.ListBox.ItemContainerGenerator.ContainerFromIndex(itemIndex) as ListBoxItem;
var templateRootBorder = VisualTreeHelper.GetChild(listBoxItemContainer, 0) as Border;
var contentHost = templateRootBorder.Child as ContentPresenter;
var textBlock = contentHost.ContentTemplate.FindName("TD", contentHost) as TextBlock;
}
Except for special use cases, it is highly recommended to use the ListBox instead of the ItemsControl. ListBox and ListView are both an extended ItemsControl. They both provide scrolling and a significantly improved performance.
First of all, you need to delete the Binding from code behind.
You can do this using Loaded event as follows:
<ItemsControl x:Name="icFiles" ItemsSource="{Binding Path=files}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
<TextBlock Loaded="ThisTextBlock_OnLoaded" x:Name="ThisTextBlock" Text="{Binding FileName}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
private void ThisTextBlock_OnLoaded(object sender, RoutedEventArgs e)
{
if (sender is TextBlock tb)
{
string? highlightText = "blue";
int highlightTextIndex = tb.Text.IndexOf(highlightText);
if (highlightTextIndex >= 0)
{
TextPointer textStartPointer = tb.ContentStart.DocumentStart.GetInsertionPosition(LogicalDirection.Forward);
TextRange? highlightTextRange = new TextRange(textStartPointer.GetPositionAtOffset(highlightTextIndex), textStartPointer.GetPositionAtOffset(highlightTextIndex + highlightText.Length));
highlightTextRange.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Blue);
}
}
}
I have a RibbonComboBox that is used to set font sizes. It has a RibbonGallery that lists the various font sizes, displayed in the appropriate FontSize:
<r:RibbonComboBox DataContext="{x:Static vm:RibbonDataModel.FontSizeComboBoxData}"
SelectionBoxWidth="30">
<r:RibbonGallery MaxColumnCount="1"
Command="{Binding Command}"
CommandParameter="{Binding SelectedItem}">
<r:RibbonGallery.GalleryItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding}"
FontSize="{Binding}" />
</Grid>
</DataTemplate>
</r:RibbonGallery.GalleryItemTemplate>
</r:RibbonGallery>
</r:RibbonComboBox>
EDIT Here is my ViewModel:
public static RibbonDataModel
{
public static GalleryData<object> FontSizeComboBoxData
{
get
{
lock (LockObject)
{
const string key = "Font Size";
if (!DataCollection.ContainsKey(key))
{
var value = new GalleryData<object>
{
Command = HtmlDocumentCommands.ChangeFontSize,
Label = "Change Font Size",
ToolTipDescription = "Set the font to a specific size.",
ToolTipTitle = "Change Font Size",
};
var fontSizes = new GalleryCategoryData<object>();
var i = 9.0;
while (i <= 30)
{
fontSizes.GalleryItemDataCollection.Add(i);
i += 0.75;
}
value.CategoryDataCollection.Add(fontSizes);
DataCollection[key] = value;
}
return DataCollection[key] as GalleryData<object>;
}
}
}
}
Everything works as expected, but after I select an item from the gallery, it shows up in the RibbonComboBox with the same huge (or tiny) FontSize as it uses in the gallery.
How can I "reset" the FontSize of the selected item to the default when it's displayed in the RibbonComboBox?
The RibbonComboBox uses a ContentPresenter to show the item you select in the RibbonGallery.
Moreover the ContentPresenter adopts the same ItemTemplate that you declared in the RibbonGallery.
This is the "core" reason of your problem.
So you can choose between two solutions to workaround the problem.
FIRST SOLUTION (the fastest one)
You can simply set the IsEditable property of your RibbonComboBox to "true". In this way the RibbonComboBox replaces the ContentPresenter with a TextBox, without using any ItemTemplate. Then the font will have the right size.
SECOND SOLUTION (the best one IMHO)
Since the ItemTemplate is used at the same from both the RibbonComboBox's ContentPresenter and the RibbonGallery, it is the point where we can try to solve the problem. The olny difference is that when the DataTemplate is placed inside the RibbonGallery, its parent is a RibbonGalleryItem.
So if its parent is not a RibbonGalleryItem, you automatically know that the DataTemplate is placed inside the ContentPresenter.
You can handle this situation by writing a simple DataTrigger.
Let's see all in the code.
I wrote a simplified ViewModel:
namespace WpfApplication1
{
public class FontSizes
{
private static FontSizes instance = new FontSizes();
private List<double> values = new List<double>();
public FontSizes()
{
double i = 9.0;
while (i <= 30)
{
values.Add(i);
i += 0.75;
}
}
public IList<double> Values
{
get
{
return values;
}
}
public static FontSizes Instance
{
get
{
return instance;
}
}
}
}
Then this is my View:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ribbon="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon"
xmlns:vm="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources />
<DockPanel>
<ribbon:RibbonComboBox Label="Select a font size:"
SelectionBoxWidth="62"
VerticalAlignment="Center">
<ribbon:RibbonGallery MaxColumnCount="1">
<ribbon:RibbonGalleryCategory DataContext="{x:Static vm:FontSizes.Instance}" ItemsSource="{Binding Path=Values, Mode=OneWay}">
<ribbon:RibbonGalleryCategory.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Name="tb" Text="{Binding}" FontSize="{Binding}" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ribbon:RibbonGalleryItem, AncestorLevel=1}}"
Value="{x:Null}">
<Setter TargetName="tb" Property="FontSize" Value="12" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ribbon:RibbonGalleryCategory.ItemTemplate>
</ribbon:RibbonGalleryCategory>
</ribbon:RibbonGallery>
</ribbon:RibbonComboBox>
</DockPanel>
</Window>
As you can see the DataTrigger is the "component" which makes the "dirty job".
Now you just need to make you your mind about which solution you prefer.
I would advise you to use the Fluent.Ribbon library instead of the Microsoft Ribbons (as they are very buggy, not well maintained and only support old styles, really trust me on this one it will just save you much trouble).
Then you simply can use this code:
<fluent:ComboBox Header="Font Size" ItemsSource="{Binding FontSizes}">
<fluent:ComboBox.ItemTemplate>
<ItemContainerTemplate>
<TextBlock FontSize="{Binding }" Text="{Binding }" />
</ItemContainerTemplate>
</fluent:ComboBox.ItemTemplate>
</fluent:ComboBox>
And get the desired result:
I followed a simple tutorial for comboboxes (http://www.wpf-tutorial.com/list-controls/combobox-control/).
Here is my XAML for the combobox :
<ComboBox Name="CoursesTeach" Grid.Row="7" Grid.Column="1" Width="150" Height="Auto" Margin="0,24">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Foreground="Black" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Code behind :
public AddTrainer()
{
InitializeComponent();
using (Model1Container context = new Model1Container())
{
foreach (var row in context.CourseSet)
{
if (row.Discipline != null)
{
CoursesTeach.ItemsSource = row.Discipline;
}
MetroCustomBox.ShowOK(row.Discipline); // i can see right values
}
}
}
But the results are just NOT in the combobox, although I can perfectly can print them.
Thanks a lot for your responses.
To add items in your Combobox by code behind you may use Items property.
With your previous code :
foreach (var row in context.CourseSet)
{
if (row.Discipline != null)
{
CoursesTeach.Items.Add(row.Discipline);
}
}
But a better way, is use ItemsSource property, with binding, or set by a List.
With your previous code :
CoursesTeach.ItemsSource = context.CourseSet.Where(row => row.Dicipline != null).Select(row => row.Dicipline).ToList();
I have on checkbox inside telerik combo control. If User click on "All" option from checkbox list then I want select all checkboxs.
checkbox values.
My Sample code is below.
<telerik:RadComboBox Name="rcbDays" Grid.Row="1" Grid.Column="1" Width="200" HorizontalAlignment="Left" ItemsSource="{Binding MonthDaysList}" VerticalAlignment="Center" >
<telerik:RadComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Name="chkDays" Content="{Binding DaysText}"
Tag="{Binding DaysValue}" Checked="chkDays_Checked" />
</StackPanel>
</DataTemplate>
</telerik:RadComboBox.ItemTemplate>
</telerik:RadComboBox>
private void chkWeeks_Checked(object sender, RoutedEventArgs e)
{
//Here I want code for selecting all checkboxes.
}
The items that you bound the ComboBox to should have a property like IsSelected, then you should bind IsChecked of the data-template CheckBox to that. Then you just need to iterate over the source collection and set IsSelected=true on all items.
e.g.
public class MyClass : MyBaseClass // Whatever you may have called it,
{
public bool IsSelected { ... }
public string DaysText { ... }
//...
}
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected}" Content="{Binding DaysText}" Tag="{Binding DaysValue}" />
</StackPanel>
</DataTemplate>
//In the handler that is supposed to select all
foreach (var item in MonthDaysList) item.IsSelected = true;
Of course the property needs to have change notifications.
(Also a note on usability: I do not thing that ComboBoxes should contain CheckBoxes, if you need multiple item selection use a ListBox)
You need to take one more property isSelected as said by H.B.
add IsChecked="{Binding IsSelected}" to CheckBox tag in xaml file. Create one property in the appropriate class i.e. public bool isSeleted.......
When you get in to event chkWeeks_Checked() in this function get reference of the ComboBox item source like objList = (TypeCastYourClassType)YourComboBox.ItemSource;... Now the objList contains all checkbox items. Iterate through objList collection and get isSeleted property for each and every single item and that's done....
In your case
MonthDayList = (TypeCastYourClassType)rcbDays.ItemSource;
for(int i=0;i<MonthDayList.Count;i++)
{
MonthDayList[i].isSelected = true;
}
Here is some good discussion for allowing multiple values to be selected in the telerik combobox.
It uses checkbox within combobox
http://codedotnets.blogspot.in/2012/02/checkboxes-in-comboxes-to-allow.html
Thanks :)
I have a button with an image inside it. This button appears on a datagrid many times for displaying the status of the row. When the user clicks the button, it changes the state of the underlying object on the row to enabled or disabled. Here is what the button looks like:
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button CommandParameter="{Binding}" HorizontalAlignment="Center">
<Image Source="{Binding Converter={StaticResource EnableDisableConverter}}" Height="25" Width="25" />
</Button>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
The converter correctly returns the proper image based on the status. The problem is that I've switched to an MVVM model and my code for changing the image won't work anymore. My previous looked like this:
Image img = (Image)btn.Content;
if (c.Status == Administration.Web.ObjectStatus.Enabled) {
img.Source = new System.Windows.Media.Imaging.BitmapImage(new Uri("/Images/enable-icon.png", UriKind.Relative));
} else {
img.Source = new System.Windows.Media.Imaging.BitmapImage(new Uri("/Images/disable-icon.png", UriKind.Relative));
}
During the command that changes the status, I've tried raising a change to the property that contains the object, but it doesn't reflect it on the UI. If I do a hard refresh of the screen, the status correctly changes. Is there a way to have rebind the image in the current situation?
Bind the source of the image to some bool Enabled property and your EnableDisableConverter can than react to that value, after each change.
XAML:
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button CommandParameter="{Binding}" HorizontalAlignment="Center">
<Image Source="{Binding IsEnabled, Converter={StaticResource EnableDisableConverter}}" Height="25" Width="25" />
</Button>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
ViewModel:
...
public bool IsEnabled
{
get
{
return _isEnabled;
}
set
{
_isEnabled=value;
NotifyPropertyChanged("IsEnabled");
}
}
...
Converter:
public object Convert(object value, ...)
{
if((bool)value)
return uri of image1;
else
return uri of image2;
}
But I don't know what are the objects in the grid and what is the ViewModel. There may be a problem. The point is to have that IsEnabled property corectly binded to those objects in the grid.