I need Attached Property that sets focus to UIElement from ViewModel.
I've created Attached property and in PropertyChangedCallback I set focus to UIElement.
private static void VoySetFocusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
if (o is UIElement)
{
if ((bool)e.NewValue)
{
(o as UIElement).Focus();
(o as UIElement).SetValue(VoySetFocusProperty, false);
}
}
}
but I want it to work like trigger in shotgun. I set true to Test in ViewModel ...
it invokes PropertyChangedCallback in MyAttachedProperties class,sets focus to UIElement
((o as UIElement).Focus();
and value of Test in ViewModel returns to false
((o as UIElement).SetValue(VoySetFocusProperty, false);)
Everything seems to work fine but SetValue doesn't change my value in ViewModel back.
Full code:
View:
<Window x:Class="WpfAttachedProperty.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfAttachedProperty"
Title="MainWindow" Height="127" Width="316">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBox local:MyAttachedProperties.VoySetFocus="{Binding Path=Test,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" Text="Focus Me"/>
<Button Grid.Row="1" Content="Click to Focus" HorizontalAlignment="Left" Margin="10" VerticalAlignment="Top" Width="75" Command="{Binding MyCommand}" />
</Grid>
Code Behind:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfAttachedProperty
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow:Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
/// <summary>
/// Command Class
/// </summary>
public class DelegateCommand:ICommand
{
private readonly Action _action;
public DelegateCommand(Action action)
{
_action = action;
}
public void Execute(object parameter)
{
_action();
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add
{
}
remove
{
}
}
}
/// <summary>
/// ViewModelClass
/// </summary>
public class ViewModel:INotifyPropertyChanged
{
private bool _test = false;
public bool Test
{
get
{
return _test;
}
set
{
_test = value;
this.NotifyPropertyChanged("Test");
}
}
public ICommand MyCommand
{
get
{
return new DelegateCommand(SetTestToTrue);
}
}
private void SetTestToTrue()
{
this.Test = true;
}
#region INotifyPropertyChanged
public void NotifyPropertyChanged(String PropertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class MyAttachedProperties
{
public static Object GetVoySetFocus(DependencyObject obj)
{
return (Object)obj.GetValue(VoySetFocusProperty);
}
public static void SetVoySetFocus(DependencyObject obj, Object value)
{
obj.SetValue(VoySetFocusProperty, value);
}
public static readonly DependencyProperty VoySetFocusProperty =
DependencyProperty.RegisterAttached("VoySetFocus", typeof(bool), typeof(MyAttachedProperties), new UIPropertyMetadata(false, new PropertyChangedCallback(VoySetFocusChanged), new CoerceValueCallback(CoerceVoySetFocus)));
private static object CoerceVoySetFocus(DependencyObject d, object baseValue)
{
return (bool)baseValue;
}
private static void VoySetFocusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
if (o is UIElement)
{
if ((bool)e.NewValue)
{
(o as UIElement).Focus();
// Doesn't set Test to false in ViewModel
(o as UIElement).SetValue(VoySetFocusProperty, false);
}
}
}
}
}
Greetings Marko.
Issue is with the line:
((o as UIElement).SetValue(VoySetFocusProperty, false);)
You should instead use SetCurrentValue to set the DP.
((o as UIElement).SetCurrentValue(VoySetFocusProperty, false);)
Explanation:
Setting the value directly of any DependencyProperty breaks the binding it with source property.
However, SetCurrentValue doesn't break the binding and push back the value back to the source property. Explanation from MSDN for SetCurrentValue:
This method is used by a component that programmatically sets the
value of one of its own properties without disabling an application's
declared use of the property. The SetCurrentValue method changes the
effective value of the property, but existing triggers, data bindings,
and styles will continue to work.
Moreover, I think setting it in PropertyChanged callback won't propagate that back to Viewmodel if do the operation synchronously since it reaches to callback from Viewmodel property setter only.
So, what we can do is do this operation asynchronously by enqueuing it on dispatcher using BeginInvoke so that it gets propagate to viewmodel Test property.
(o as UIElement).Focus();
Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate
{
// Doesn't set Test to false in ViewModel
(o as UIElement).SetCurrentValue(VoySetFocusProperty, false);
});
Related
I am a C# newbie trying to build a simple MVVM app, and I am having trouble tying events in my XAML View to methods in my Model or ViewModel. I understand why MVVM is used and feel like I get the broad strokes of how to put an MVVM app together, but I am lost in the details. I apologize in advance if it looks like I have no idea what I'm doing, but I don't, despite lots of reading up on the subject.
I want btnUpdate_Click in MainScreenViewModel to execute when the button is clicked, but I get the error
MC6005 Click="vm:btnUpdate_Click" is not valid. 'vm:btnUpdate_Click' is not a valid event handler method name. Only instance methods on the generated or code-behind class are valid.
If my classes are public and in the same namespace, what do I need to do to make them visible from my View? I don't want to move methods back to the MainWindow class.
<Window x:Class="SFM_Calculator.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:SFM_Calculator"
xmlns:vm="MainScreenViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
>
<Window.DataContext>
<local:SFMModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
...
<ColumnDefinition/>
</Grid.ColumnDefinitions>
...
</Grid.RowDefinitions>
...
<Button
Grid.Column="1"
Grid.Row="2"
x:Name="btnUpdate"
Content="Update"
Click="vm:btnUpdate_Click"
/>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Linq;
using System.ComponentModel;
namespace SFM_Calculator
{
public class MainScreenViewModel : INotifyPropertyChanged
{
private void btnUpdate_Click(object sender, System.Windows.RoutedEventArgs e)
{
TestInt = 999;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public SFMModel sfmModel = new SFMModel();
private int _testInt;
public int TestInt
{
get { return _testInt; }
set { _testInt = value; }
}
public MainScreenViewModel()
{
Debug.WriteLine("Got here.");
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace SFM_Calculator
{
public class SFMModel : INotifyPropertyChanged
{
private int _tprop;
public int TProp
{
get { return _tprop; }
set { _tprop = value; }
}
public SFMModel ()
{
TProp = 69;
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Configuration;
namespace SFM_Calculator
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
Your viewModel should expose a command, not a method. Plus you should access the exposed command via binding mechanism.
Sample Command that implements required ICommand interface
internal class Command : ICommand
{
private readonly Action execute;
public Command(Action execute)
{
this.execute = execute;
}
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter)
{
return true;
}
public void Execute(object? parameter)
{
execute();
}
}
Sample ViewModel that exposes ICommand (not a regular method as in your example). It will change the value of Text property after clicking the button - just to show that it works.
internal class ViewModel : INotifyPropertyChanged
{
public string Text { get; private set; }
public ICommand AwesomeCommand { get; }
public ViewModel()
{
AwesomeCommand = new Command(() => {
Text = "Button clicked";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
});
}
public event PropertyChangedEventHandler? PropertyChanged;
}
MainWindow:
<StackPanel>
<Button Command="{Binding AwesomeCommand}"></Button>
<Label Content="{Binding Text}" Height="100"></Label>
</StackPanel>
code-behind MainWindow, to hook-up ViewModel and the view (MainWindow):
public MainWindow()
{
DataContext = new ViewModel();
InitializeComponent();
}
I am building a WinUI 3 application that presents a user with a list of Animal IDs, along with a Button beside each ID that the user can click to view information about that specific Animal. I am using an ItemsRepeater, whose ItemsSource is bound to an ObservableCollection<Animal> inside an AnimalViewModel, so I can create a list of TextBox with a Button beside it using a DataTemplate -- the TextBox will have as its value the Animal.ID. I also want to use the MVVM design pattern to accomplish this, but this is my first time trying it.
For now, I want the ability to click a Button and have it display a dialog box with the Animal.ID it corresponds to (the TextBox adjacent to it). I have tried implementing this by using an ICommand and defining a function to display a MessageDialog in my AnimalViewModel, then defining the custom ICommand in its own file.
The issue seems to be that since I have bound my ItemsRepeater to ObservableCollection<Animal> in my AnimalViewModel, I can't bind the Button to the command in my AnimalViewModel. This leads me to believe I am not using MVVM correctly or I have improperly structured my code, but I am not sure what to change/how to move forward.
Error
BindingExpression path error: 'DisplayIDsCommand' property not found on 'MAIT.Models.Animal'
Below is the relevant code. I am able to create the list but I cannot get the Button to exhibit the desired behavior (please forgive any glaring errors, I had to simplify the code for this question):
MainWindow.xaml
<Window
x:Class="MAIT.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MAIT"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:l="using:MAIT.Models"
mc:Ignorable="d">
<Grid x:Name="MainGrid">
<Grid.Resources>
<muxc:StackLayout x:Name="VerticalStackLayout" Orientation="Vertical" Spacing="8"/>
<DataTemplate x:Key="AnimalTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="text" Text="{Binding ID}"></TextBlock>
<Button Command="{Binding DisplayIDsCommand}" CommandParameter="{Binding ElementName=text, Path=Text}">View</Button>-
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto" HorizontalAlignment="Center" VerticalAlignment="Center"
HorizontalScrollMode="Auto"
IsVerticalScrollChainingEnabled="False"
MaxHeight="500">
<muxc:ItemsRepeater
ItemsSource="{Binding Path=Animals}"
Layout="{StaticResource VerticalStackLayout}"
ItemTemplate="{StaticResource AnimalTemplate}"/>
</ScrollViewer>
</Grid>
</Window>
MainWindow.xaml.cs
using Microsoft.UI.Xaml;
using MAIT.ViewModels;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace MAIT
{
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
MainGrid.DataContext = new AnimalViewModel();
}
}
}
Animal.cs
using System.ComponentModel;
using System.Diagnostics;
namespace MAIT.Models
{
internal class Animal : INotifyPropertyChanged
{
string _ID;
public Animal(string id)
{
ID = id;
}
public string ID
{
get
{
return _ID;
}
set
{
_ID = value;
OnPropertyChanged("ID");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
AnimalViewModel.cs
using MAIT.Commands;
using MAIT.Models;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Data.OleDb;
using System.Diagnostics;
using Windows.UI.Popups;
namespace MAIT.ViewModels
{
internal class AnimalViewModel : INotifyPropertyChanged
{
public IDsCommand DisplayIDsCommand;
public AnimalViewModel()
{
Animals = new ObservableCollection<Animal>();
DisplayIDsCommand = new IDsCommand(DisplayIDs);
GetAnimals();
}
private void GetAnimals()
{
for(int i = 0; i < 3; i++)
{
Animal animal = new Animal(i.ToString());
Animals.Add(animal);
}
}
public async void DisplayIDs(string id)
{
MessageDialog t = new MessageDialog(id);
await t.ShowAsync();
}
private ObservableCollection<Animal> _Animals;
public ObservableCollection<Animal> Animals
{
get
{
return _Animals;
}
set
{
_Animals = value;
OnPropertyChanged("Animals");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
IDsCommand.cs
using MAIT.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace MAIT.Commands
{
internal class IDsCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private Action<string> _Execute;
public IDsCommand(Action<string> execute)
{
_Execute = execute;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_Execute.Invoke(parameter as string);
}
}
}
your first WPF appears to be well-on-its-way.
If you want to bind to your command, you will need to use a private field along with the getter and setter, such as with the Animals property, and call OnPropertyChanged in its setter as well. This will let the button know when a command is assigned!
Here are a few friendly pointers if you are interested about learning MVVM:
Consider replacing the using in the local parameter to xmlns:local="clr-namespace:MAIT".
In MVVM DataContext property can be set in the Window's constructor but usually it's put in the XALM above <Grid> as
<Window.DataContext>
<viewModels:AnimalViewModel/>
</Window.DataContext>
Because this context will not allow you to use local.ViewModels, you can declare another xmlns as xmlns:viewModels="clr-namespace:MAIT.ViewModels"
If you are using recent .NET versions, you might appreciate using PropertyChanged?.Invoke(this, new(propertyName)); as the method body for OnPropertyChanged. Moreover, you can use System.Runtime.CompilerServices to automatically get the calling member's name by changing the OnPropertyChanged's signature to private void OnPropertyChanged([CallerMemberName] string propertyName = "").
To be able to bind the Command property to a source property of a parent element, you need to get a reference to the parent element somehow. In Win UI, you cannot use something RelativeSource AncestorType=ItemsRepeater.
You could workaround this by creating an attached property that sets the DataContext of the Button to the parent ItemsRepeater:
public static class AncestorSource
{
public static readonly DependencyProperty AncestorTypeProperty =
DependencyProperty.RegisterAttached(
"AncestorType",
typeof(Type),
typeof(AncestorSource),
new PropertyMetadata(default(Type), OnAncestorTypeChanged)
);
public static void SetAncestorType(FrameworkElement element, Type value) =>
element.SetValue(AncestorTypeProperty, value);
public static Type GetAncestorType(FrameworkElement element) =>
(Type)element.GetValue(AncestorTypeProperty);
private static void OnAncestorTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement target = (FrameworkElement)d;
if (target.IsLoaded)
SetDataContext(target);
else
target.Loaded += OnTargetLoaded;
}
private static void OnTargetLoaded(object sender, RoutedEventArgs e)
{
FrameworkElement target = (FrameworkElement)sender;
target.Loaded -= OnTargetLoaded;
SetDataContext(target);
}
private static void SetDataContext(FrameworkElement target)
{
Type ancestorType = GetAncestorType(target);
if (ancestorType != null)
target.DataContext = FindParent(target, ancestorType);
}
private static object FindParent(DependencyObject dependencyObject, Type ancestorType)
{
DependencyObject parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null)
return null;
if (ancestorType.IsAssignableFrom(parent.GetType()))
return parent;
return FindParent(parent, ancestorType);
}
}
Usage:
<DataTemplate x:Key="AnimalTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="text" Text="{Binding ID}"></TextBlock>
<Button local:AncestorSource.AncestorType="muxc:ItemsRepeater"
Command="{Binding DataContext.DisplayIDsCommand}"
CommandParameter="{Binding ElementName=text, Path=Text}">View</Button>-
</StackPanel>
</DataTemplate>
Please refer to this blog post for more information.
I'm stumped on this one. Why would a command parameter be continuously empty despite being bound to a property that has a value.
XAML
<UserControl x:Class="CatalogInterface.ctlMainButtonPanel"
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"
xmlns:local="clr-namespace:CatalogInterface"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="MainButtonPanel">
<UserControl.Resources>
<local:MainButtonPanelViewModel x:Key="ViewModel" />
<!--#region Button Style-->
<!--#region FocusVisual-->
<!--#endregion FocusVisual-->
<!--#region Button-->
<!--#endregion Button-->
<!--#endregion Button Style-->
</UserControl.Resources>
<Grid DataContext="{StaticResource ViewModel}">
<!--/////////////////////////////////////////////////////////////////////////////////-->
<!--////////This is the button that won't pass the CommandParameter//////////////////-->
<!--/////////////////////////////////////////////////////////////////////////////////-->
<Button x:Name="cmdConvertToFBook"
CommandParameter="{Binding SelectedDocument}"
Command="{Binding ConvertToFacebookCommand}"
Content="CONVERT TO FACEBOOK"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Grid.Column="0" Grid.Row="2" />
</UserControl>
View Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace CatalogInterface
{
class MainButtonPanelViewModel : ViewModelBase
{
private Messanger _messanger = Messanger.Set_Messanger();
/////////////////////////////////////////////////////////////////////////////////
////////This is the property I'm binding my commandparameter to//////////////////
/////////////////////////////////////////////////////////////////////////////////
private object _selectedDocument
public object SelectedDocument
{
get { return _selectedDocument; }
set { _selectedDocument = value; }
}
public ConvertToFacebookCommand ConvertToFacebookCommand { get; set; }
public MainButtonPanelViewModel()
{
ConvertToFacebookCommand = new ConvertToFacebookCommand();
_messanger.Register(OnSentMessage, "DirFilesListBox_SelectedDocumentChanged");
}
public void OnSentMessage(object source, MessageEventArgs e)
{
_selectedDocument = e.MessageObject;
}
}
public class ConvertToFacebookCommand : SingleFuncitonBaseCommand
{
public override void ButtonFunction(object parameter)
{
ConvertToFacebookFn.Convert(parameter);
}
public override void NotExacutable(object parameter)
{
ButtonNotExacutable.Report(parameter);
}
}
}
ViewModelBase
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace CatalogInterface
{
public abstract class SingleFuncitonBaseCommand : ICommand
{
private bool _canExecute { get; set; } = true;
public SingleFuncitonBaseCommand()
{
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public virtual bool CanExecute(object parameter)
{
return true;
}
public virtual void Execute(object parameter)
{
if (_canExecute && parameter != null)
{
try
{
_canExecute = false;
ButtonFunction(parameter);
}
finally
{
_canExecute = true;
}
return;
}
NotExacutable(parameter);
}
public abstract void ButtonFunction(object parameter);
public abstract void NotExacutable(object parameter);
}
}
My SelectedDocument property is being set by the OnSentMessage event that updates the property when the user clicks on a File in a list box. I believe the object is a FileInfo object. I can confirm the event is firing properly and the property is being set.
Also if i change my SelectedDocument property to a readonly property and set it to a FileInfo object it then does get passed to the parameter.
Is it possible for my OnSentMessage event and CommandParameter binding to be operating on two different objects?
How can I debug this further?
Thanks All.
I'm building an application using CefSharp and need to provide text search functionality to user just like Google Chrome has.
Can any one help me with the implementation of text search in CefSharp?
You can do it simple just add two buttons and a textbox on your form.
first buttons for next result,second buttons for previous result and textbox for search text provider.
On textbox's KeyUp event run below code
if (tosBrowserSearchTxt.Text.Length <= 0)
{
//this will clear all search result
webBrowserChromium.StopFinding(true);
}
else
{
webBrowserChromium.Find(0, tosBrowserSearchTxt.Text, true, false,false);
}
On next button click run below code
webBrowserChromium.Find(0, tosBrowserSearchTxt.Text, true, false, false);
On previous button click run below code
webBrowserChromium.Find(0, tosBrowserSearchTxt.Text, false, false, false);
When user type any character into text box KeyUp event's code will search that text, by using next and previous button you can navigate from one result to another.
I've built this demo application using CefSharp 47.0.3, hopefully this is what you're looking for.
The view:
<Window x:Class="CefSharpSearchDemo.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:wpf="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cefSharpSearchDemo="clr-namespace:CefSharpSearchDemo"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
d:DataContext="{d:DesignInstance {x:Type cefSharpSearchDemo:MainWindowViewModel}}">
<DockPanel>
<DockPanel DockPanel.Dock="Top">
<Button Content="Next" DockPanel.Dock="Right" Command="{Binding ElementName=SearchBehavior, Path=NextCommand}" />
<Button Content="Previous" DockPanel.Dock="Right" Command="{Binding ElementName=SearchBehavior, Path=PreviousCommand}" />
<TextBox DockPanel.Dock="Right" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</DockPanel>
<wpf:ChromiumWebBrowser x:Name="wb" DockPanel.Dock="Bottom"
Address="http://stackoverflow.com">
<i:Interaction.Behaviors>
<cefSharpSearchDemo:ChromiumWebBrowserSearchBehavior x:Name="SearchBehavior" SearchText="{Binding SearchText}" />
</i:Interaction.Behaviors>
</wpf:ChromiumWebBrowser>
</DockPanel>
</Window>
The code-behind for the view:
namespace CefSharpSearchDemo
{
using System.Windows;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
}
The view model:
namespace CefSharpSearchDemo
{
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _searchText;
public string SearchText
{
get { return _searchText; }
set
{
_searchText = value;
NotifyPropertyChanged();
}
}
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And now the important part. As you could see in the view there is a behavior attached to the ChromiumWebBrowser:
namespace CefSharpSearchDemo
{
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;
using CefSharp;
using CefSharp.Wpf;
public class ChromiumWebBrowserSearchBehavior : Behavior<ChromiumWebBrowser>
{
private bool _isSearchEnabled;
public ChromiumWebBrowserSearchBehavior()
{
NextCommand = new DelegateCommand(OnNext);
PreviousCommand = new DelegateCommand(OnPrevious);
}
private void OnNext()
{
AssociatedObject.Find(identifier: 1, searchText: SearchText, forward: true, matchCase: false, findNext: true);
}
private void OnPrevious()
{
AssociatedObject.Find(identifier: 1, searchText: SearchText, forward: false, matchCase: false, findNext: true);
}
protected override void OnAttached()
{
AssociatedObject.FrameLoadEnd += ChromiumWebBrowserOnFrameLoadEnd;
}
private void ChromiumWebBrowserOnFrameLoadEnd(object sender, FrameLoadEndEventArgs frameLoadEndEventArgs)
{
_isSearchEnabled = frameLoadEndEventArgs.Frame.IsMain;
Dispatcher.Invoke(() =>
{
if (_isSearchEnabled && !string.IsNullOrEmpty(SearchText))
{
AssociatedObject.Find(1, SearchText, true, false, false);
}
});
}
public static readonly DependencyProperty SearchTextProperty = DependencyProperty.Register(
"SearchText", typeof(string), typeof(ChromiumWebBrowserSearchBehavior), new PropertyMetadata(default(string), OnSearchTextChanged));
public string SearchText
{
get { return (string)GetValue(SearchTextProperty); }
set { SetValue(SearchTextProperty, value); }
}
public static readonly DependencyProperty NextCommandProperty = DependencyProperty.Register(
"NextCommand", typeof (ICommand), typeof (ChromiumWebBrowserSearchBehavior), new PropertyMetadata(default(ICommand)));
public ICommand NextCommand
{
get { return (ICommand) GetValue(NextCommandProperty); }
set { SetValue(NextCommandProperty, value); }
}
public static readonly DependencyProperty PreviousCommandProperty = DependencyProperty.Register(
"PreviousCommand", typeof (ICommand), typeof (ChromiumWebBrowserSearchBehavior), new PropertyMetadata(default(ICommand)));
public ICommand PreviousCommand
{
get { return (ICommand) GetValue(PreviousCommandProperty); }
set { SetValue(PreviousCommandProperty, value); }
}
private static void OnSearchTextChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var behavior = dependencyObject as ChromiumWebBrowserSearchBehavior;
if (behavior != null && behavior._isSearchEnabled)
{
var newSearchText = dependencyPropertyChangedEventArgs.NewValue as string;
if (string.IsNullOrEmpty(newSearchText))
{
behavior.AssociatedObject.StopFinding(true);
}
else
{
behavior.AssociatedObject.Find(1, newSearchText, true, false, false);
}
}
}
protected override void OnDetaching()
{
AssociatedObject.FrameLoadEnd -= ChromiumWebBrowserOnFrameLoadEnd;
}
}
}
And the minor additional code for the DelegateCommand:
namespace CefSharpSearchDemo
{
using System;
using System.Windows.Input;
public class DelegateCommand : ICommand
{
private readonly Action _action;
public DelegateCommand(Action action)
{
_action = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_action();
}
public event EventHandler CanExecuteChanged;
}
}
The resulting application has a TextBox on the top and two buttons labeled "Previous" and "Next" next to it.
The main area is a CefSharp browser which loads http://www.stackoverflow.com.
You can type into the TextBox and it will search in the browser (and highlight the scrollbar where the hits are, just like in Chrome). You can then press the Next/Previous buttons to cycle through the hits.
I hope this helps in developing your own solution.
All this said, let me just note that next time if you ask a question, actually provide some code what you tried, or try to ask a more specific question, because this is probably too broad for this site. Anyway I leave this here, maybe others will find it useful as well.
Important lesson: there are some methods exposed on ChromiumWebBrowser that you can use to implement the search functionality (namely: Find and StopFinding).
In a wpf dialog window I have a checkbox that enables and disables a textbox. The textbox has ValidatesOnDataErrors set to True. Via IDataErrorInfo I check the value of this textbox only, if the checkbox is checked.
My problem is that if the user checks the checkbox there is no new validation on textbox performed and therefor I dont get this red frame indicating an error.
For demonstration here is a small sample:
<Window x:Class="Validation.ValidationWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ValidationWindow" Height="300" Width="300">
<DockPanel LastChildFill="False">
<CheckBox DockPanel.Dock="Top" IsChecked="{Binding InputAllowed}">Input allowed</CheckBox>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Label>InputValue</Label>
<TextBox Text="{Binding InputValue, ValidatesOnDataErrors=True}" IsEnabled="{Binding InputAllowed}" Width="50"/>
</StackPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Label>InputValue2</Label>
<TextBox Text="{Binding InputValue2, ValidatesOnDataErrors=True}" Width="50"/>
</StackPanel>
<Button DockPanel.Dock="Bottom" Click="OnOk">Ok</Button>
</DockPanel>
</Window>
code behind:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Validation
{
/// <summary>
/// Interaction logic for ValidationWindow.xaml
/// </summary>
public partial class ValidationWindow : Window, IDataErrorInfo
{
public bool InputAllowed
{
get { return (bool)GetValue(InputAllowedProperty); }
set { SetValue(InputAllowedProperty, value); }
}
public static readonly DependencyProperty InputAllowedProperty =
DependencyProperty.Register("InputAllowed", typeof(bool), typeof(ValidationWindow), new PropertyMetadata(false));
public int InputValue
{
get { return (int)GetValue(InputValueProperty); }
set { SetValue(InputValueProperty, value); }
}
public static readonly DependencyProperty InputValueProperty =
DependencyProperty.Register("InputValue", typeof(int), typeof(ValidationWindow), new PropertyMetadata(0));
public int InputValue2
{
get { return (int)GetValue(InputValue2Property); }
set { SetValue(InputValue2Property, value); }
}
public static readonly DependencyProperty InputValue2Property =
DependencyProperty.Register("InputValue2", typeof(int), typeof(ValidationWindow), new PropertyMetadata(0));
public ValidationWindow()
{
InitializeComponent();
DataContext = this;
}
private void OnOk(object sender, RoutedEventArgs e)
{
string msg = Error;
if(!string.IsNullOrEmpty(Error))
{
MessageBox.Show(Error);
return;
}
DialogResult = true;
}
#region IDataErrorInfo Members
public string Error
{
get { return ((IDataErrorInfo)this)[null]; }
}
public string this[string columnName]
{
get
{
string msg = string.Empty;
if(string.IsNullOrEmpty(columnName))
{
msg += ((IDataErrorInfo)this)["InputValue"];
msg += ((IDataErrorInfo)this)["InputValue2"];
}
else
{
switch(columnName)
{
case "InputValue":
if(InputAllowed)
{
if(InputValue <= 0)
{
msg += "InputValue must be greater that 0!";
}
}
break;
case "InputValue2":
if(InputValue2 <= 0)
{
msg += "InputValue2 must be greater that 0!";
}
break;
}
}
return msg;
}
}
#endregion
}
}
We've been using this to force validation after programatcially changing text, should work as well if you call it in reponse to your checkbox's events:
var binding = someTextBox.GetBindingExpression( TextBox.TextProperty );
if( binding == null )
return;
binding.UpdateSource();
No actual difference from stijn's solution but since we're all a little lazy:
public static class DependencyPropertyExtensions
{
public static bool UpdateSource(this FrameworkElement source, DependencyProperty property)
{
var binding = source.GetBindingExpression(property);
if (binding != null)
{
binding.UpdateSource();
return true;
}
return false;
}
}