I need to insert UIElements into a Grid that does not get generated until runtime. More specifically, I need to add UIElements to the RowDefinitions I create after I determine how many elements need to be displayed. Is there a way to contorl the Grid.Row and Grid.Column and Grid.RowSpan like in XAML for objects in C#? If I am going about this wrong, please let me know. I can not use a StackPanel (I am creating an dynamic accordian panel and it messes with the animation).
Right now what happens is that I generate the number of RowDefinitions at runtime and add UIElements as the children. This isn't working, all the UIElements end up in the first row layered on top of each other.
Here is an example of what I am trying:
public partial class Page : UserControl
{
string[] _names = new string[] { "one", "two", "three" };
public Page()
{
InitializeComponent();
BuildGrid();
}
public void BuildGrid()
{
LayoutRoot.ShowGridLines = true;
foreach (string s in _names)
{
LayoutRoot.RowDefinitions.Add(new RowDefinition());
LayoutRoot.Children.Add(new Button());
}
}
}
Thanks!
Apart from the Grid not really being the right tool for the job (a ListView would be) you need to tell the Button which row it belongs to.
public void BuildGrid()
{
LayoutRoot.ShowGridLines = true;
int rowIndex = 0;
foreach (string s in _names)
{
LayoutRoot.RowDefinitions.Add(new RowDefinition());
var btn = new Button()
LayoutRoot.Children.Add(btn);
Grid.SetRow(btn, rowIndex);
rowIndex += 1;
}
}
The best way to do what you're looking for is to follow the pattern below:
Page.xaml:
<UserControl x:Class="SilverlightApplication1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<data:DataGrid x:Name="DataGridTest" />
</Grid>
</UserControl>
Page.xaml.cs:
public partial class Page : UserControl
{
string[] _names = new string[] { "one", "two", "three" };
public Page()
{
InitializeComponent();
BuildGrid();
}
public void BuildGrid()
{
DataGridTest.ItemsSource = _names;
}
}
This builds the rows dynamically from the contents of your string array. In future an even better way would be to use an ObservableCollection where T implements INotifyPropertyChanged. This will notify the DataGrid to update it's rows if you remove or add an item from the collection and also when properties of T change.
To further customize the UIElements used to display things you can use a DataGridTemplateColumn:
<data:DataGridTemplateColumn Header="Symbol">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding PutNameOfPropertyHere}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
I have never used this stuff, but shouldn't you be adding the Button to the RowDefinitions, instead of the Children? (just a logical observation)
Related
i wanna do a log lister app in C#. I have a listbox called LogBox and i wanna color multiple times a row. like: "[04:30:20] - Admin: Hello" but each variable should be different color at row in Listbox.
How should i do this with button action?
I tried LogBox.Items.Add(LogBox.ForeColor = color.red + "[" etc etc etc. but its doesn't work.
I guess you might be looking for something like this.
It can be easily achieved if you have a model class which is bound to your ListBox.
Follow the below steps
Step 1 - Create a model class, say suppose "ListBoxItemModel.cs"
public class ListBoxItemModel
{
public string Text { get; set; }
public Brush ForegroundBrush { get; set; }
}
Note:- I am not following any MVVM approach here for demo. If you are familiar then you can implement with this code.
Step 2 - Create a window with ListBox and define a DataTemplate for your Model class as below in your MainWindow.
Assign the DataTemplate to your ListBox ItemTemplate property.
<Window x:Class="SO61263305.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SO61263305"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate x:Key="LocalTemplate" DataType="local:ListBoxItemModel">
<TextBlock Text="{Binding Text}" Foreground="{Binding ForegroundBrush}" />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ListBox x:Name="ItemsListBox" Grid.Row="0" Height="200" Width="200"
ItemTemplate="{StaticResource LocalTemplate}"/>
</Grid>
Step 3 - Create an List of "ListBoxItemModel" and bound to ListBox from code-behind of your window or user control. In my case it is MainWindow.xaml.cs
private void LoadDataObjects()
{
var items = new List<ListBoxItemModel>();
var item = new ListBoxItemModel()
{
Text = "John ABCD 1",
ForegroundBrush = new SolidColorBrush(Color.FromRgb(0, 0, 0))
};
items.Add(item);
item = new ListBoxItemModel()
{
Text = "John ABCD 2",
ForegroundBrush = new SolidColorBrush(Color.FromRgb(200, 79, 24))
};
items.Add(item);
ItemsListBox.ItemsSource = items;
}
In the above method you need to add each item with the Text which you wanted to display and Foreground Brush.
Step 4 - Call above method from your constructor of code-behind or else you can call from any other events like Button click to load the data to a listbox.
See below of my complete MainWindow.xaml.cs (code behind of the MainWindow)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
LoadDataObjects();
}
private void LoadDataObjects()
{
var items = new List<ListBoxItemModel>();
var item = new ListBoxItemModel()
{
Text = "John ABCD 1",
ForegroundBrush = new SolidColorBrush(Color.FromRgb(0, 0, 0))
};
items.Add(item);
item = new ListBoxItemModel()
{
Text = "John ABCD 2",
ForegroundBrush = new SolidColorBrush(Color.FromRgb(200, 79, 24))
};
items.Add(item);
ItemsListBox.ItemsSource = items;
}
}
Hope this should give you some idea and you can improve your requirements on top of it.
Give a try and let us know in case if you face any difficulties.
I'm trying to bind an ObservableCollection<T> to a DataGrid in WPF.
Below the DataGrid, there are fields to edit the currently selected item from the DataGridlike so:
So the generic T of the ObservableCollection<T> has the following properties:
- Title (Überschrift)
- Description (Beschreibung)
- Path (Pfad)
and it also has a property Reihenfolge which means Order.
With the yellow arrows, I want to be able to modify the order of the entries.
Unfortunately, the ObservableCollection doesn't have an OrderBy-method...
I've tried the following:
In XAML I have defined a CollectionViewSource like this:
<CollectionViewSource Source="{Binding Bilder}" x:Key="Pictures">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Reihenfolge" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
And I have binded the DataGrid to this CollectionViewSource
<DataGrid Grid.Column="0" Grid.Row="1"
Name="PictureDataGrid"
ItemsSource="{Binding Source={StaticResource Pictures}}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserAddRows="false"
SelectedItem="{Binding SelectedBild}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
...
In the ViewModel, I have these properties:
public ObservableCollection<BildNotifiableModel> Bilder { get; set; }
public BildNotifiableModel SelectedBild { get; set; }
and two methods which are called with DelegateCommands that update the order
private void MoveSeiteUp()
{
const int smallestReihenfolge = 1;
if (this.SelectedBild.Reihenfolge > smallestReihenfolge) {
var bildToSwapReihenfolgeWith = this.Bilder.Single(b => b.Reihenfolge == this.SelectedBild.Reihenfolge - 1);
this.SelectedBild.Reihenfolge--;
bildToSwapReihenfolgeWith.Reihenfolge++;
RaisePropertyChanged(nameof(this.Bilder));
}
}
private void MoveSeiteDown()
{
if (this.SelectedBild.Reihenfolge < MaxAllowedImages) {
var bildToSwapReihenfolgeWith = this.Bilder.Single(b => b.Reihenfolge == this.SelectedBild.Reihenfolge + 1);
this.SelectedBild.Reihenfolge++;
bildToSwapReihenfolgeWith.Reihenfolge--;
RaisePropertyChanged(nameof(this.Bilder));
}
}
The order gets updated correctly, but unfortunately, the view doesn't reflect the changes... only after closing and reopening the view, the entries in the DataGrid are in the correct order.
What am I doing wrong here?
How can I make the DataGrid update, when changing the order?
Thanks in advance
I think the problem is that the CollectionView doesn't listen for the PropertyChanged-Events from its elements and also RaisePropertyChanged(nameof(this.Bilder)); dosen't work because the CollectionView is not really changed.
I would recomend to create the CollectionView in code via CollectionViewSource.GetDefaultView(list). So you can control the CollectionView from your model and call ICollectionView.Refresh if needed.
In your Methods, create a new Collection and add it to "Bilder". Just raising the PropertyChanged will execute an evaluation for referential equality. If it is the same - which it will be, if you just move items inside around - it will not update the DataGrid.
If you are not using the ObservableCollections attributes, like automatically updates, when items are added or removed, you might also change it to a "normal" List.
private void MoveSeiteUp()
{
const int smallestReihenfolge = 1;
if (this.SelectedBild.Reihenfolge > smallestReihenfolge) {
var bildToSwapReihenfolgeWith = this.Bilder.Single(b => b.Reihenfolge == this.SelectedBild.Reihenfolge - 1);
this.SelectedBild.Reihenfolge--;
bildToSwapReihenfolgeWith.Reihenfolge++;
this.Bilder = new ObservableCollection<BildNotifiableModel> (this.Bilder);
RaisePropertyChanged(nameof(this.Bilder));
}
}
private void MoveSeiteDown()
{
if (this.SelectedBild.Reihenfolge < MaxAllowedImages) {
var bildToSwapReihenfolgeWith = this.Bilder.Single(b => b.Reihenfolge == this.SelectedBild.Reihenfolge + 1);
this.SelectedBild.Reihenfolge++;
bildToSwapReihenfolgeWith.Reihenfolge--;
this.Bilder = new ObservableCollection<BildNotifiableModel> (this.Bilder);
RaisePropertyChanged(nameof(this.Bilder));
}
}
I have a listview in my UWP (Windows 10) application.
Ideally it will load 100 items when application starts.
When list is scrolled to bottom i.e. to the last item in listview, API call will go & will load another 100 items & so on..
Here is my code :
<ListView x:Name="lstSuggestions" Height="200" Width="250" Foreground="#333eb4" HorizontalAlignment="Left" Margin="60,10" SelectionChanged="lstSuggestions_SelectionChanged"></ListView>
following call binds the listview (first 100 items on app start) :
public async void GetData(string Id, string limit)
{
string mainUrlForSuggestions = ApiUrl + "&id=" + d;
string finalSelCharas = "";
using (var httpClient = new HttpClient())
{
var dataUri = await httpClient.GetStringAsync(mainUrlForSuggestions);
JsonObject jsonObject = JsonObject.Parse(dataUri.ToString());
JsonArray jsonArray = jsonObject["payload"].GetArray();
foreach (JsonValue groupValue in jsonArray)
{
JsonObject groupObject = groupValue.GetObject();
lstSuggestionsAdd.Add(new SuggestedWords { Name = groupObject.GetNamedString("sug_name"), ID = groupObject.GetNamedString("id") });
}
lstSuggestions.ItemsSource = lstSuggestionsAdd;
}
}
on app start limit is 100, once list reaches to an end, it must set limit to 200 or next 100 items and make an API call again.
I tried to achieve this with pointerEntered event. But, couldn't achieve the said functionality as it only matches the height assigned to listview with pointer height, so that wont work as scrollviewer height can vary. I even tried to get access to scrollviewer, but couldn't!
I have also referred following URL's : How do I allow a UWP ListView to scroll past the last item? && Detect when WPF listview scrollbar is at the bottom? && https://social.msdn.microsoft.com/Forums/windows/en-US/63b4b530-61d8-477f-af96-87e33260c919/uwa-how-to-detect-the-end-and-the-start-of-listview-and-load-more-data-items?forum=wpdevelop
But none of them actually worked in my case.
I tried to find an event to achieve this functionality, but didn't find any.
Can anyone give an idea about how to detect if listview scrolling reached to an end (last item in the listview)???
Note that i am working on windows 10 UWP application & not win 8
It's a bit different; it uses the ListView's incremental loading functionality to create a infinite scrolling list.
This means you won't have as much control of loading the data as you assumed in your question, but still I think it will suit your needs:
It uses MVVM bindings so no typical UI events are used. If you don't know about MVVM, try to duckduckgo it a bit.
First some XAML, the default main page:
<Page
x:Class="App6.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App6"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:ViewModel, IsDesignTimeCreatable=True}">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{Binding Items}"
DataFetchSize="1"
IncrementalLoadingTrigger="Edge"
IncrementalLoadingThreshold="5">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Text}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
Note the
ItemsSource="{Binding Items}"
DataFetchSize="1"
IncrementalLoadingTrigger="Edge"
IncrementalLoadingThreshold="5"
ItemSource will bind to the items collection, its used in the item template
DataFetchSize, the amount to fetch when the end is reached: in PAGES. (confused me for a moment)
IncrementalLoadingTrigger, see msdn
IncrementalLoadingThreshold, see msdn
Then..., the code:
First a custom observable collection: Here is also your load routine:
public class IncrementalLoadingCollection : ObservableCollection<Item>, ISupportIncrementalLoading
{
uint x = 0; //just for the example
public bool HasMoreItems { get { return x < 10000; } } //maximum
//the count is the number requested
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
return AsyncInfo.Run(async cancelToken =>
{
//here you need to do your loading
for (var c = x; c < x + count; c++)
{
//add your newly loaded item to the collection
Add(new Item()
{
Text = c.ToString()
});
}
x += count;
//return the actual number of items loaded (here it's just maxed)
return new LoadMoreItemsResult { Count = count };
});
}
}
We are using a new Item class, so lets define it:
//the type which is being used to display the data
//you could extend it to use images and stuff
public class Item
{
public string Text { get; set; }
}
Lets create a viewmodel:
public class ViewModel
{
public ViewModel()
{
Items = new IncrementalLoadingCollection();
}
//using the custom collection: these are your loaded items
public IncrementalLoadingCollection Items { get; set; }
}
Wrap it up in the code behind: we use the data context:
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
DataContext= new ViewModel(); //using a viewmodel as data context
}
}
More info can be found here
I'm currently building a product configurator in C#/WPF. The Product is something like a custom outlet strip. It's made out of an aluminum profile with variable length. You can choose the outlets and place them along the aluminum profile.
We already have the data model. We have the class "profile" with properties like "length" and the class "items" with properties like "position", "type" etc.
I made a funktion to visualise the data. I have a MainCanvas where im drawing the profile as rectangle and for the witdth I'm using the property "width". The items that belong to that product are in a list. I'm drawing the items with using a for-loop to insert a wpf for each plug in the list "items".
When I'm changing the data, I have to clear the canvas and redraw. I think the next step will be binding the variables of the class to the WPF properties, for example the position of a plug or the lenth of the profile.
Later, the plugs should be select/dragable to change their position (in a grid of 5mm , for example). That's the pint where I'm stuck. I know I can select and drag the plugs with actions like "OnLeftMouseButton". The question now is: In what type of container should I put the plugs in?
I thought I could wrap the WPF-Code of a plug inside a UserControl. Is that the right approach for that? As far as I know, I can make a UserControl selectable with Hittest. The Hittestresult will be put in a list selected. For displaying the selection I could use the Borderthickness/Brush of the UserControl. For dragging, I could change the Position with a ManupilationDelta (and change the binded Position-Variable). Because the count of the plugs is variable, I have to generate the UserControls from C#-Code.
I know that Hittest is not easy to implement working with UserControls, because they are not really "visible".
I'm kind of new to C# and I have a hard time finding someone on the internet with similar problems or projects, maybe because I'm searching for the wrong words. Are my assumptions correct? What WPF controls would you use for that?
I used this answer and add few modifications.
I used an ItemControl to create the view containing your different objects. That way, you can add images just by adding object to a list. The container is a Canvas, but it can be anything since the position is controlled by RenderTransform :
<ItemsControl Name="MainView" ItemsSource="{Binding ListObjects}">
<ItemsControl.ItemsPanel >
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:MVDragableObject}">
<local:DragableObject/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the code behind. Some basic ModelView:
public class MVObjectManager
{
public ObservableCollection<MVDragableObject> ListObjects { get; set; }
public MVObjectManager()
{
ListObjects = new ObservableCollection<MVDragableObject>();
}
}
public class MVDragableObject
{
}
And the code to fill and bind the container. You can notice that I added 3 items to the collection:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MVObjectManager Manager=new MVObjectManager();
Manager.ListObjects.Add(new MVDragableObject());
Manager.ListObjects.Add(new MVDragableObject());
Manager.ListObjects.Add(new MVDragableObject());
MainView.DataContext = Manager;
}
}
I defined a very simple UserControl. It is up to you to customize it:
<UserControl x:Class="StackFill.DragableObject"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d">
<Grid>
<Rectangle Fill="Red" Height="30" Width="30"/>
</Grid>
</UserControl>
And here is the code behind that manage the Drag and Drop behavior:
public partial class DragableObject : UserControl
{
public DragableObject()
{
InitializeComponent();
this.MouseLeftButtonDown += new MouseButtonEventHandler(DragableObject_MouseLeftButtonDown);
this.MouseLeftButtonUp += new MouseButtonEventHandler(DragableObject_MouseLeftButtonUp);
this.MouseMove += new MouseEventHandler(DragableObject_MouseMove);
}
protected bool isDragging;
private Point clickPosition;
private void DragableObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
isDragging = true;
var draggableControl = sender as UserControl;
clickPosition = e.GetPosition(this.Parent as UIElement);
draggableControl.CaptureMouse();
}
private void DragableObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
isDragging = false;
var draggable = sender as UserControl;
draggable.ReleaseMouseCapture();
}
private void DragableObject_MouseMove(object sender, MouseEventArgs e)
{
var draggableControl = sender as UserControl;
if (isDragging && draggableControl != null)
{
Point currentPosition = e.GetPosition(this.Parent as UIElement);
var transform = draggableControl.RenderTransform as TranslateTransform;
if (transform == null)
{
transform = new TranslateTransform();
draggableControl.RenderTransform = transform;
}
transform.X = snapPosition(currentPosition.X - clickPosition.X, 10);
transform.Y = snapPosition(currentPosition.Y - clickPosition.Y, 10);
}
}
private double snapPosition(double position, double gridSize)
{
return (Math.Truncate(position / gridSize) * gridSize);
}
}
You can control the snap precision by changing the gridSize argument.
I need to set the font family for the next text to be written in a RichTextBox.
I tried setting that with...
<RichTextBox x:Name="RichTextEditor" MaxWidth="1000" SpellCheck.IsEnabled="True"
FontFamily="{Binding ElementName=TextFontComboBox, Path=SelectedItem}"
FontSize="{Binding ElementName=TextSizeComboBox, Path=SelectedValue}"
Width="Auto" Height="Auto" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto" />
...but it changed the whole text. I suppose that with the Selection property I can restrict the change to be applied just to the selected area. But how for the next -not yet typed- text?
In order to set the FontFamily based on the cursor position you need to define a custom control with a dependency property that helps insert a new Run section by overriding the OnTextInput method.
I included most of the code, you'll need to modify the namespaces to fit your development environment.
The code uses a ViewModel to manage the available fonts and manage if the font changed.
This code is only a prototype and does not deal with focusing issues between the two controls.
To use this code:
1- Type some text in the RichTectBox.
2- Change the font in the ComboBox.
3- Tab back to the RichTextBox.
4- Type some more text.
Here is the custom RichTextBox control:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
namespace RichTextboxFont.Views
{
public class RichTextBoxCustom : RichTextBox
{
public static readonly DependencyProperty CurrentFontFamilyProperty =
DependencyProperty.Register("CurrentFontFamily",
typeof(FontFamily), typeof
(RichTextBoxCustom),
new FrameworkPropertyMetadata(new FontFamily("Tahoma"),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnCurrentFontChanged)));
public FontFamily CurrentFontFamily
{
get
{
return (FontFamily)GetValue(CurrentFontFamilyProperty);
}
set
{
SetValue(CurrentFontFamilyProperty, value);
}
}
private static void OnCurrentFontChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{}
protected override void OnTextInput(TextCompositionEventArgs e)
{
ViewModels.MainViewModel mwvm = this.DataContext as ViewModels.MainViewModel;
if ((mwvm != null) && (mwvm.FontChanged))
{
TextPointer textPointer = this.CaretPosition.GetInsertionPosition(LogicalDirection.Forward);
Run run = new Run(e.Text, textPointer);
run.FontFamily = this.CurrentFontFamily;
this.CaretPosition = run.ElementEnd;
mwvm.FontChanged = false;
}
else
{
base.OnTextInput(e);
}
}
}
}
Here is the XAML:
<Window x:Class="RichTextboxFont.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RichTextboxFont.Views"
xmlns:ViewModels="clr-namespace:RichTextboxFont.ViewModels"
Title="Main Window"
Height="400" Width="800">
<DockPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox ItemsSource="{Binding Path=Fonts}"
SelectedItem="{Binding Path=SelectedFont, Mode=TwoWay}"/>
<local:RichTextBoxCustom Grid.Row="1"
CurrentFontFamily="{Binding Path=SelectedFont, Mode=TwoWay}"
FontSize="30"/>
</Grid>
</DockPanel>
</Window>
Here is the ViewModel:
If you do not use view models, let me know and I'll add the base class code too; otherwise, google/stackoverflow can help you too.
using System.Collections.ObjectModel;
using System.Windows.Media;
namespace RichTextboxFont.ViewModels
{
public class MainViewModel : ViewModelBase
{
#region Constructor
public MainViewModel()
{
FontFamily f1 = new FontFamily("Georgia");
_fonts.Add(f1);
FontFamily f2 = new FontFamily("Tahoma");
_fonts.Add(f2);
}
private ObservableCollection<FontFamily> _fonts = new ObservableCollection<FontFamily>();
public ObservableCollection<FontFamily> Fonts
{
get
{
return _fonts;
}
set
{
_fonts = value;
OnPropertyChanged("Fonts");
}
}
private FontFamily _selectedFont = new FontFamily("Tahoma");
public FontFamily SelectedFont
{
get
{
return _selectedFont;
}
set
{
_selectedFont = value;
FontChanged = true;
OnPropertyChanged("SelectedFont");
}
}
private bool _fontChanged = false;
public bool FontChanged
{
get
{
return _fontChanged;
}
set
{
_fontChanged = value;
OnPropertyChanged("FontChanged");
}
}
#endregion
}
}
Here is the Window code-behind where I initialise the ViewModel:
using System.Windows;
namespace RichTextboxFont.Views
{
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
this.DataContext = new ViewModels.MainViewModel();
}
}
}
There's a much easier way to do this: Implement a toolbar for your RichTextBox.
Unlike WinForms, the RichTextBox in WPF doesn't come with a toolbar by default, but it's really easy to create one yourself. The RichTextBox automatically handles many EditingCommands, so it's just a matter of creating a toolbar and some buttons. Microsoft has provided sample code for this at the bottom of the RichTextBox Overview on MSDN.
Unfortunately, those editing commands don't include setting the FontFace property of the selection, though you can create a ComboBox on the toolbar that can trigger the change with an event handler in the codebehind file.
That's the approach taken in this CodePlex article by Gregor Pross: WPF RichTextEditor
The project is commented in German, but the source itself is very clearly written. The codebehind used for his font selector ComboBox looks like this:
private void Fonttype_DropDownClosed(object sender, EventArgs e)
{
string fontName = (string)Fonttype.SelectedItem;
if (fontName != null)
{
RichTextControl.Selection.ApplyPropertyValue(System.Windows.Controls.RichTextBox.FontFamilyProperty, fontName);
RichTextControl.Focus();
}
}
The main reason that people struggle with the FontFace selection is that after the font selection has been made, you must return focus to the RichTextBox. If the user must manually press tab or click into the RichTextBox, a new text selection gets created and you lose the formatting options you've chosen.
One of the answers to this StackOverflow question discusses that problem.
WPF Richtextbox FontFace/FontSize
This isn't exactly a trivial answer.
To do inline text formatting in a Rich TextBox like you want you will have to modify the Document property of the RichTextBox. Very simply, something like this will work
<RichTextBox >
<RichTextBox.Document>
<FlowDocument>
<Paragraph>
<Run>Something</Run>
<Run FontWeight="Bold">Something Else</Run>
</Paragraph>
</FlowDocument>
</RichTextBox.Document>
</RichTextBox>
I think you could create a custom Control that creates a new block element and sets the font properties you need based on the user input.
For example, If the user types something then presses bold. You would want to wrap the previous text in a run and create a new run element setting the FontWeight to bold then the subsequent text will be wrapped in the bolded run.
Again, not a trivial solution but I can't think of any other way to accomplish what you are after.