Insert text at caret from button in RichTextBox mvvm using wpf toolkit - mvvm

I have selected text from a ListBox that I want to insert into a RichTextBox at the caret position. I can get the selected text to be inserted at the end of the text string.
I am not sure how to pass the RichTextBox caret position to my view model.
Here is part of my code for the project.
<Button x:Name="AddItemBtn" Content="Add Item" HorizontalAlignment="Left" Margin="417,10,0,0" VerticalAlignment="Top" Width="100" Command="{Binding AddItemBtn}" CommandParameter="{Binding ElementName=AddItemList,Path=SelectedItem}"/>
<wpftoolkit:RichTextBox Grid.Column="0" Text="{Binding TestText, UpdateSourceTrigger=PropertyChanged}" x:Name="MyEditor" ScrollViewer.VerticalScrollBarVisibility="Auto" Margin="0" Height="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" IsDocumentEnabled="True" AcceptsTab="True" AcceptsReturn="True" >
<wpftoolkit:RichTextBox.Resources>
<Style TargetType="{x:Type Paragraph}">
<Setter Property="Margin" Value="0" ></Setter>
<Setter Property="FontSize" Value="15"></Setter>
</Style>
</wpftoolkit:RichTextBox.Resources>
<wpftoolkit:RichTextBox.TextFormatter>
<wpftoolkit:PlainTextFormatter/>
</wpftoolkit:RichTextBox.TextFormatter>
</wpftoolkit:RichTextBox>
Here is the view model part.
private string _testText;
public string TestText
{
get
{
return _testText;
}
set
{
//_testText = _testText + value;
SetProperty(ref _testText, value);
}
}
public ICommand AddItemBtn
{
get;
set;
}
public void addItem(Tabbed selectedItem)
{
if (selectedItem != null)
{
MessageBox.Show(selectedItem.Command);
if (TestText != null)
{
TestText = TestText.ToString() + selectedItem.Command;
}
else
{
TestText = selectedItem.Command;
}
}
}
I tried a flowdocument but still could not get the parameters to pass correctly.

I like to put a function on the view model that is setup in the view code behind.
public class MainViewModel : ViewModelBase
{
public Func<int> GetCarrotPosition { get; set; }
//...
Looks like you can get the number of characters into the text string by getting the offset from the document start start to the current position
public MainWindow()
{
// InitializeComponent stuff..
var castedContext = (MainViewModel)DataContext;
castedContext.GetCarrotPosition = () =>
{
// Placing the cursor at the start of the text returns a value of 2, so I subtract 2 to get the current cursor location
return MyRichTextBox.CaretPosition.DocumentStart.GetOffsetToPosition(MyRichTextBox.CaretPosition) - 2;
};
//...
Finally, call the GetCarrotPosition() function in your command
var carrotPosition = GetCarrotPosition();
TestText.Insert(carrotPosition, selectedItem.Command);
Creating delegates on the view model that get wired up in the view code behind is the sexiest MVVM way of working with UI elements I know of.

Related

Bind ProgressRing to mvvm

I am downloading data from an api, and displaying that data in the view. As I wait I want to display a ProgressRing, but when I bind it dosen't work
Bind the ring to the cityData property, with a two way mode and update it when the property changes
Created a new property in the VM, that is true by default and it will turn false when I get the data back
XAML
<TextBox x:Name="currentLocation"
PlaceholderText="Please wait..."
IsReadOnly="True"
Margin="20"
Width="300"
Text="{Binding cityData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<ListView RelativePanel.Below="currentLocation"
x:Name="ForecastList"
Margin="20"
SelectedItem="{Binding currentDay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding dailyForecasts}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<TextBlock x:Name="dateTB" Text="{Binding Date.DayOfWeek}" />
<TextBlock x:Name="highTB" Text="{Binding Temperature.Maximum.Value, Converter={StaticResource cv}}" FontSize="10" />
<TextBlock x:Name="lowTB" FontSize="10" Text="{Binding Temperature.Minimum.Value, Converter={StaticResource cv}}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ProgressRing x:Name="pRing" RelativePanel.Above="ForecastList" RelativePanel.AlignHorizontalCenterWith="currentLocation" IsActive="{Binding ring, Mode=TwoWay}" RelativePanel.Below="currentLocation" />
VM
public class WeatherVM: INotifyPropertyChanged
{
public AccuWeather accuWeather { get; set; }
private string _cityData;
public string cityData
{
get { return _cityData; }
set
{
if (value != _cityData)
{
_cityData = value;
onPropertyChanged("cityData");
GetWeatherData();
}
}
}
private DailyForecast _currentDay;
public DailyForecast currentDay
{
get { return _currentDay; }
set
{
if (value != _currentDay) \
{
_currentDay = value;
onPropertyChanged("currentDay");
}
}
}
public bool ring { get; set; } = true;
public ObservableCollection<DailyForecast> dailyForecasts { get; set; }
public WeatherVM()
{
GetCuurentLocation();
dailyForecasts = new ObservableCollection<DailyForecast>();
}
private async void GetCuurentLocation() {
cityData = await BingLocator.GetCityData();
}
public async void GetWeatherData() {
var geoposition = await LocationManager.GetGeopositionAsync();
var currentLocationKey = await WeatherAPI.GetCityDstaAsync(geoposition.Coordinate.Point.Position.Latitude, geoposition.Coordinate.Point.Position.Longitude);
var weatherData = await WeatherAPI.GetWeatherAsync(currentLocationKey.Key);
if (weatherData != null) {
foreach (var item in weatherData.DailyForecasts) {
dailyForecasts.Add(item);
}
}
currentDay = dailyForecasts[0];
ring = false;
}
public event PropertyChangedEventHandler PropertyChanged;
private void onPropertyChanged(string property) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
}
The progress ring appears when the app launch, and disappear when the data is returned
From your code, it seems to be a layout issue. First you put ListView below currentLocation, then set ProgressRing above ListView and below currentLocation, so the height of ProgressRing wil be zero. I'm not clear about your layout, you can try to only set RelativePanel.Above="ForecastList" for ProgressRing to see if it will appear.
You can use Windows Community Toolkit control for showing progress ring (Busy indicator).
<Page ...
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"/>
<controls:Loading x:Name="LoadingControl" IsLoading="{Binding IsBusy}">
<!-- Loading screen content -->
</controls:Loading>

UWP - How to specify the order of updates when use x:Bind?

I'm developing a UWP app and I'm facing a problem. The app uses the MVVM pattern with Template10. I have created a similar solution that recreates the problem that I'm facing. In that solution, a list of orders are displayed, the user chooses an order and then click the "Edit" button. Then a second page is displayed and pre-loaded with the previous selected order, in this second page the user can edit the order. The problem is in the second page, the data bound to comboboxes doesn't show. Maybe the problem is related to this question. In my case, the SelectedValue is set before the ItemsSource. After debugging, I have reached these lines of code in OrderEditionPage.g.cs:
private void Update_ViewModel(global::ComboApp.ViewModels.OrderEditionPageViewModel obj, int phase)
{
this.bindingsTracking.UpdateChildListeners_ViewModel(obj);
if (obj != null)
{
if ((phase & (NOT_PHASED | DATA_CHANGED | (1 << 0))) != 0)
{
this.Update_ViewModel_SelectedOrder(obj.SelectedOrder, phase);
}
if ((phase & (NOT_PHASED | (1 << 0))) != 0)
{
this.Update_ViewModel_BusinessAssociates(obj.BusinessAssociates, phase);
this.Update_ViewModel_TransactionTypes(obj.TransactionTypes, phase);
this.Update_ViewModel_OrderTypes(obj.OrderTypes, phase);
this.Update_ViewModel_ShowSelectedOrder(obj.ShowSelectedOrder, phase);
}
}
}
If I could achieve this line of code be executed at last, my problem would be solved: this.Update_ViewModel_SelectedOrder(obj.SelectedOrder, phase);
How could I achieve this? How does Visual Studio determine the order of this lines?
OrderEditionPage.xaml
<Page
x:Class="ComboApp.Views.OrderEditionPage"
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:myconverters="using:ComboApp.Converters"
xmlns:t10converters="using:Template10.Converters"
mc:Ignorable="d">
<Page.Resources>
<t10converters:ChangeTypeConverter x:Key="TypeConverter" />
<myconverters:DateTimeConverter x:Key="DateTimeConverter" />
</Page.Resources>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel
Padding="15, 5"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBox
Header="Order #"
Margin="5"
Width="150"
HorizontalAlignment="Left"
Text="{x:Bind ViewModel.SelectedOrder.ExternalId, Mode=TwoWay}" />
<ComboBox
Header="Business Associate"
Margin="5"
MinWidth="300"
SelectedValuePath="BusinessAssociateId"
DisplayMemberPath="Name1"
ItemsSource="{x:Bind ViewModel.BusinessAssociates}"
SelectedValue="{x:Bind ViewModel.SelectedOrder.BusinessAssociateId, Mode=TwoWay, Converter={StaticResource TypeConverter}}" />
<DatePicker
Header="Delivery Date"
Margin="5"
MinWidth="0"
Width="200"
Date="{x:Bind ViewModel.SelectedOrder.DeliveryDate, Mode=TwoWay, Converter={StaticResource DateTimeConverter}}" />
<ComboBox
Header="Transaction"
MinWidth="200"
Margin="5"
SelectedValuePath="Value"
DisplayMemberPath="Display"
ItemsSource="{x:Bind ViewModel.TransactionTypes}"
SelectedValue="{x:Bind ViewModel.SelectedOrder.TransactionType, Mode=TwoWay}" />
<TextBox
Header="Priority"
Margin="5"
MaxWidth="150"
HorizontalAlignment="Left"
Text="{x:Bind ViewModel.SelectedOrder.Priority}" />
<ComboBox
Header="Type"
Margin="5"
MinWidth="200"
SelectedValuePath="Value"
DisplayMemberPath="Display"
ItemsSource="{x:Bind ViewModel.OrderTypes}"
SelectedValue="{x:Bind ViewModel.SelectedOrder.OrderType, Mode=TwoWay}" />
<TextBox
Header="Information"
Margin="5"
Height="100"
AcceptsReturn="True"
TextWrapping="Wrap"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Text="{x:Bind ViewModel.SelectedOrder.Information, Mode=TwoWay}" />
<Button
Margin="5"
Content="Show"
Width="100"
HorizontalAlignment="Right"
Command="{x:Bind ViewModel.ShowSelectedOrder}" />
</StackPanel>
</ScrollViewer>
</Page>
OrderEditionPage.xaml.cs
using ComboApp.ViewModels;
using Windows.UI.Xaml.Controls;
namespace ComboApp.Views
{
public sealed partial class OrderEditionPage : Page
{
public OrderEditionPageViewModel ViewModel => DataContext as OrderEditionPageViewModel;
public OrderEditionPage()
{
this.InitializeComponent();
}
}
}
OrderEditionPageViewModel.cs
using ComboApp.Models;
using ComboApp.Services;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Template10.Mvvm;
using Template10.Utils;
using Windows.UI.Xaml.Navigation;
namespace ComboApp.ViewModels
{
public class OrderEditionPageViewModel
: ViewModelBase
{
private IBusinessAssociateService businessAssociateService;
private Order selectedOrder;
public Order SelectedOrder
{
get { return selectedOrder; }
set { Set(ref selectedOrder, value); }
}
public ObservableCollection<object> TransactionTypes { get; set; } = new ObservableCollection<object>();
public ObservableCollection<object> OrderTypes { get; set; } = new ObservableCollection<object>();
public ObservableCollection<BusinessAssociate> BusinessAssociates { get; set; } = new ObservableCollection<BusinessAssociate>();
public OrderEditionPageViewModel(IBusinessAssociateService businessAssociateService)
{
this.businessAssociateService = businessAssociateService;
TransactionTypes.Add(new { Value = "I", Display = "Incoming" });
TransactionTypes.Add(new { Value = "O", Display = "Outgoing" });
TransactionTypes.Add(new { Value = "T", Display = "Transfer" });
OrderTypes.Add(new { Value = "M", Display = "Manual" });
OrderTypes.Add(new { Value = "A", Display = "Automatic" });
OrderTypes.Add(new { Value = "S", Display = "Semi-automatic" });
}
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
{
// Loading buiness associates
var response = await businessAssociateService.GetNextPageAsync();
if (response.IsSuccessful)
{
BusinessAssociates.AddRange(response.Result.Items);
}
SelectedOrder = (Order)parameter;
await base.OnNavigatedToAsync(parameter, mode, state);
}
private DelegateCommand showSelectedOrder;
public DelegateCommand ShowSelectedOrder => showSelectedOrder ?? (showSelectedOrder = new DelegateCommand(async () =>
{
await Views.MessageBox.ShowAsync(JsonConvert.SerializeObject(SelectedOrder, Formatting.Indented));
}));
}
}
It is a known issue of x:Bind when the SelectedValue of a ComboBox is sometimes set before its ItemsSource, you can read more about it here.
As a workaround you can use Bindings instead of x:Bind, but make sure that ItemsSource binding is placed before SelectedValue binding in XAML.
Alternatively you can try calling Bindings.Update() in the Page_Loaded event of your second page.

Setting focus to UI control in WPF MVVM Way on Validation.HasError

Problem: Validation.HasError automatically highlights the control that has Error via INotifyDataErrorInfo implementation.
My problem is i need to set focus on that specific control when it has ERror.
How do I do That?
I have gone through several articles in Stackoverflow and other sites and i finally wish to address this problem.
<Style TargetType="TextBox" >
<Setter Property="OverridesDefaultStyle" Value="false"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Margin" Value="5,3" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}"/>
</Trigger>
</Style.Triggers>
</Style>
Setting FocusedElement did the trick. :)
This can also be used to set focus using a boolean property in ViewModel via DataTrigger than a simple trigger.
Wpf MVVM Used FocusExtension Behaviors
public static class FocusExtension
{
public static bool GetIsFocused(DependencyObject obj)
{
return (bool)obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
}
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached(
"IsFocused", typeof(bool), typeof(FocusExtension),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
private static void OnIsFocusedPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement)d;
if ((bool)e.NewValue)
{
uie.Focus();
}
}
}
Xaml Code
<TextBox
behavior:FocusExtension.IsFocused="{Binding NameFocus}"
Text="{Binding Customer_Name,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged }"
x:Name="txtname"
CharacterCasing="Upper"
Grid.Column="3"
Grid.Row="1"
TextWrapping="Wrap"
BorderThickness="1,1,1,0.5"
>
</TextBox>
MVVM Propery in View Model
public const string NameFocusPropertyName = "NameFocus";
private bool _NameFocus = default(bool);
public bool NameFocus
{
get
{
return _NameFocus;
}
set
{
if (_NameFocus == value)
{
return;
}
_NameFocus = value;
RaisePropertyChanged(NameFocusPropertyName);
}
}
Set Load Event
NameFocus=true

AutoCompleteBox Don't SelectedItem on Down/Up Key

I use AutoCompleteBox in MVVM and i want to execute something only if the user click on the Item or if the user press Enter.
But now when I use the down\Up Key on the keyboard the selectedItem property changes...
My controls :
<Controls:AutoCompleteBox ItemsSource="{Binding IndicationDtos, Mode=TwoWay}"
Width="100" SelectedItem="{Binding IndicationSelected, Mode=TwoWay}"
ValueMemberPath="Diagnosis" Text="{Binding Criteria, Mode=TwoWay}" MinimumPopulateDelay="250"/>
What can I do to make the property "SelectedItem" is assigned only on Enter or click?
If you have any question...
thanks a lot
In your SelectedItem binding, you can use:
SelectedItem="{Binding IndicationSelected, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
That way selected item only changes when you focus on something else
I found solution i created new class.
Like this :
public class AutoCompleteBoxEx : AutoCompleteBox
{
public static readonly DependencyProperty SelectionBoxItemProperty =
DependencyProperty.Register(
"SelectionBoxItem",
typeof(object),
typeof(AutoCompleteBox),
new PropertyMetadata(OnSelectionBoxItemPropertyChanged));
public object SelectionBoxItem
{
get
{
return GetValue(SelectionBoxItemProperty);
}
set
{
SetValue(SelectionBoxItemProperty, value);
}
}
protected override void OnDropDownClosing(RoutedPropertyChangingEventArgs<bool> e)
{
base.OnDropDownClosing(e);
SelectionBoxItem = SelectionAdapter.SelectedItem;
}
private static void OnSelectionBoxItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
}

Why does DelegateCommand not work the same for Button and MenuItem?

This code shows that the Delegate Command from the Visual Studio MVVM template works differently when used with MenuItem and Button:
with Button, the command method has access to changed OnPropertyChanged values on the ViewModel
when using MenuItem, however, the command method does not have access to changed OnPropertyChanged values
Does anyone know why this is the case?
MainView.xaml:
<Window x:Class="TestCommand82828.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:TestCommand82828.Commands"
Title="Main Window" Height="400" Width="800">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Command="{Binding DoSomethingCommand}" Header="Do Something" />
</MenuItem>
</Menu>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
<Button Command="{Binding DoSomethingCommand}" Content="test"/>
<TextBlock Text="{Binding Output}"/>
<TextBox Text="{Binding TheInput}"/>
</StackPanel>
</DockPanel>
</Window>
MainViewModel.cs:
using System;
using System.Windows;
using System.Windows.Input;
using TestCommand82828.Commands;
namespace TestCommand82828.ViewModels
{
public class MainViewModel : ViewModelBase
{
#region ViewModelProperty: TheInput
private string _theInput;
public string TheInput
{
get
{
return _theInput;
}
set
{
_theInput = value;
OnPropertyChanged("TheInput");
}
}
#endregion
#region DelegateCommand: DoSomething
private DelegateCommand doSomethingCommand;
public ICommand DoSomethingCommand
{
get
{
if (doSomethingCommand == null)
{
doSomethingCommand = new DelegateCommand(DoSomething, CanDoSomething);
}
return doSomethingCommand;
}
}
private void DoSomething()
{
Output = "did something, the input was: " + _theInput;
}
private bool CanDoSomething()
{
return true;
}
#endregion
#region ViewModelProperty: Output
private string _output;
public string Output
{
get
{
return _output;
}
set
{
_output = value;
OnPropertyChanged("Output");
}
}
#endregion
}
}
I think what you're seeing is because MenuItems don't take focus away from a TextBox, and by default, a TextBox only pushes changes back to its bound source when focus shifts away from it. So when you click the button, the focus shifts to the button, writing the TextBox's value back to _theInput. When you click the MenuItem, however, focus remains in the TextBox so the value is not written.
Try changing your TextBox declaration to:
<TextBox Text="{Binding TheInput,UpdateSourceTrigger=PropertyChanged}"/>
Or alternatively, try switching to DelegateCommand<t>, which can receive a parameter, and pass the TextBox's text to it:
<MenuItem Command="{Binding DoSomethingCommand}"
CommandParameter="{Binding Text,ElementName=inputTextBox}" />
...
<TextBox x:Name="inputTextBox" Text="{Binding TheInput}" />