Working on a similar concept. Here is what I did. The below solution also somewhat adheres to MVVM architectural pattern if that's your thing.
You'll need the following.
Step 1:
Add the following for you XAML.
<TextBox Text="{Binding Path=MessageText, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
AcceptsReturn="False" AcceptsTab="True" TextWrapping="Wrap" SpellCheck.IsEnabled ="True">
<TextBox.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="3"/>
</Style>
</TextBox.Resources>
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding SendMessageCommand, Mode=OneWay}" />
<KeyBinding Gesture="Shift+Return" Command="{Binding NewLineCommand, Mode=OneWay}" CommandParameter="{Binding Path=., Mode=OneWay, ElementName=chattext_field}" />
</TextBox.InputBindings>
Step 2 : Create your view model if you don't already have one. In my example, it called AppViewModel.
class AppViewModel : INotifyPropertyChanged
{
private string messageText = string.Empty;
public string MessageText
{
get { return messageText; }
set
{
messageText = value;
NotifyPropertyChanged();
}
}
public ICommand SendMessageCommand { get; private set; }
public ICommand NewLineCommand { get; private set; }
public void Load()
{
NewLineCommand = new CustomCommand(p =>
{
System.Windows.Controls.TextBox txtB = p as System.Windows.Controls.TextBox;
if (txtB == null)
return;
var caretIdx = txtB.CaretIndex;
if (string.IsNullOrEmpty(MessageText))
MessageText += "\r";
else
MessageText = MessageText.Insert(caretIdx, "\r");
txtB.CaretIndex = caretIdx + 1;
});
SendMessageCommand = new CustomCommand(p =>
{
try
{
// your send message code here
}
catch (LogException ex)
{
System.Windows.MessageBox.Show($"Message sending failure.\n{ex}", "Message Center", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Step 3 :
When you load your user control/View using the view model. Initialize/Load the view model when the view is ready.
public partial class MyChatControl : UserControl
{
public MyChatControl()
{
InitializeComponent();
this.Loaded += MyChatControl_Loaded;
}
private void MyChatControl_Loaded(object sender, RoutedEventArgs e)
{
try
{
AppViewModel model = new AppViewModel();
model.Load();
this.DataContext = model;
}
catch (LogException ex)
{
MessageBox.Show($"Failed control content load.\n{ex}", "Failed", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
Almost forgot, here is my "CustomCommand" implementation if you don't have one yet. I have a Async version called "CustomAsyncCommand" as well if you need.
// Interface
public interface ICustomCommand : ICommand
{
event EventHandler<object> Executed;
}
// Command Class
public class CustomCommand : ICustomCommand
{
#region Private Fields
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
#endregion
#region Constructor
public CustomCommand(Action<object> execute) : this(execute, null)
{
}
public CustomCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute ?? (x => true);
}
#endregion
#region Public Methods
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter = null)
{
Refresh();
_execute(parameter);
Executed?.Invoke(this, parameter);
Refresh();
}
public void Refresh()
{
CommandManager.InvalidateRequerySuggested();
}
#endregion
#region Events
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public event EventHandler<object> Executed;
#endregion
}