Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Made an MVVM friendly example. #30

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using CefSharp.Wpf;
using System.Windows;
using System.Windows.Interactivity;

namespace CefSharp.MinimalExample.Wpf.Binding.Behaviors
{
public class LoadHtmlBehavior : Behavior<ChromiumWebBrowser>
{
// Using a DependencyProperty as the backing store for Html. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HtmlProperty =
DependencyProperty.Register(
"Html",
typeof(string),
typeof(LoadHtmlBehavior),
new PropertyMetadata(string.Empty, OnHtmlChanged));

// Using a DependencyProperty as the backing store for HtmlUrl. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HtmlUrlProperty =
DependencyProperty.Register(
"HtmlUrl",
typeof(string),
typeof(LoadHtmlBehavior),
new PropertyMetadata(string.Empty));

public string Html
{
get { return (string)GetValue(HtmlProperty); }
set { SetValue(HtmlProperty, value); }
}

public string HtmlUrl
{
get { return (string)GetValue(HtmlUrlProperty); }
set { SetValue(HtmlUrlProperty, value); }
}

private static void OnHtmlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null || string.IsNullOrWhiteSpace((string)e.NewValue))
{
return;
}

(d as ChromiumWebBrowser)?.LoadHtml((string)e.NewValue, "http://test/page");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would appear that DependencyObject d is actually the behavior (not really surprising).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I have seen this, I changed the code from an Attached Property Behavior to a Blend Behavior and things got messy. I am not very experienced with Blend Behaviors and lacks tutorials on how to get the AttachedObject inside the change handler, I will bring it back to an Attached Property Behavior and save the trouble.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private static void OnHtmlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if (e.NewValue == null || string.IsNullOrWhiteSpace((string)e.NewValue))
    {
        return;
    }

    var behaviour = (LoadHtmlBehavior)d;

    var chromiumWebBrowser = behaviour.AssociatedObject as ChromiumWebBrowser;
    if(chromiumWebBrowser != null)
    {
        chromiumWebBrowser.LoadHtml((string)e.NewValue, behaviour.HtmlUrl);
    }
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good catch 👍

Copy link
Author

@feinstein feinstein Oct 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed as

(d as LoadHtmlBehavior)?.AssociatedObject.LoadHtml((string)e.NewValue, "about:blank");

}
}
}
24 changes: 24 additions & 0 deletions CefSharp.MinimalExample.Wpf/Binding/BindableBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace CefSharp.MinimalExample.Wpf.Binding
{
public class BindableBase : INotifyPropertyChanged
{
protected virtual void SetProperty<T>(ref T member, T val,
[CallerMemberName] string propertyName = null)
{
if (object.Equals(member, val)) { return; }

member = val;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
}
98 changes: 98 additions & 0 deletions CefSharp.MinimalExample.Wpf/Binding/RelayCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
using System.Windows.Input;

namespace CefSharp.MinimalExample.Wpf.Binding
{
public class RelayCommand : ICommand
{
Action _TargetExecuteMethod;
Func<bool> _TargetCanExecuteMethod;

public RelayCommand(Action executeMethod)
{
_TargetExecuteMethod = executeMethod;
}

public RelayCommand(Action executeMethod, Func<bool> canExecuteMethod)
{
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}

public void RaiseCanExecuteChanged()
{
CanExecuteChanged(this, EventArgs.Empty);
}
#region ICommand Members

bool ICommand.CanExecute(object parameter)
{
if (_TargetCanExecuteMethod != null)
{
return _TargetCanExecuteMethod();
}
if (_TargetExecuteMethod != null)
{
return true;
}
return false;
}

// Beware - should use weak references if command instance lifetime is longer than lifetime of UI objects that get hooked up to command
// Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };

void ICommand.Execute(object parameter)
{
_TargetExecuteMethod?.Invoke();
}
#endregion
}

public class RelayCommand<T> : ICommand
{
Action<T> _TargetExecuteMethod;
Func<T, bool> _TargetCanExecuteMethod;

public RelayCommand(Action<T> executeMethod)
{
_TargetExecuteMethod = executeMethod;
}

public RelayCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
{
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}

public void RaiseCanExecuteChanged()
{
CanExecuteChanged(this, EventArgs.Empty);
}
#region ICommand Members

bool ICommand.CanExecute(object parameter)
{
if (_TargetCanExecuteMethod != null)
{
T tparm = (T)parameter;
return _TargetCanExecuteMethod(tparm);
}
if (_TargetExecuteMethod != null)
{
return true;
}
return false;
}

// Beware - should use weak references if command instance lifetime is longer than lifetime of UI objects that get hooked up to command
// Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };

void ICommand.Execute(object parameter)
{
_TargetExecuteMethod?.Invoke((T)parameter);
}
#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Expression.Interactions, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these dlls now part of .Net or still additional? If they're not then adding them makes deployment just that bit more complicated.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are set through "Add Reference...", not something that I download and add as a Reference, but still not something that comes right out of the box.

I can make an Attached Property Behavior instead if you prefer, it should work just out of the box, just not something Blend could interact with nicely.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They come standard with VS these days, far as I'm aware they're not part of .Net, though been a while since I actually did any actual WPF development so things could easily have changed.

How do other projects handle using them as references? When they're included indirectly as they would be when using CefSharp would uses run into problems deploying their app with dlls not being available on non dev machines?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK they will be exported into the bin folder along with all the rest, just like a framework you might have added as a Nuget Package.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you include them directly, they'll use Copy Local = True, if they're included indirectly, like through a 3rd party lib then are they copied?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I Cloned the original project and it doesn't build, some projects appear to be missing, I didn't read any tutorials on the process of contributing to the main project, so I am sorry if this is explained there somewhere.

You'll need to install the VC++ Development tools (from memory they are included by default with VC++, maybe someone unchecked them upon install?)

Adding this behavior into the main project would just need to add the references to these new dlls to the Nuget package, what else should be needed?

I am not understanding your concern with deployment, maybe I am not experienced enough on deployment. IMHO the dlls will be copied to the output folder and the Nuget Package will include them, the only side effect I can see is increasing the project deployment size

It's quite a bit more complicated than that. Using MahApps as an example, if I search their issue tracker e.g. https://github.com/MahApps/MahApps.Metro/search?utf8=%E2%9C%93&q=System.Windows.Interactivity.dll&type=Issues I see there are a number of versioning issues.

but I can fix this changing the Behavior from a Blend Behavior to an Attached Property Behavior

This maybe the simplest option for now.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have Visual C++ 2015 in my Visual Studio about page and I do c++ development on this machine some times... that's weird.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it was unable to load the VC++ projects. I usually use 2013 as the VS version is linked to the VC++ version (which VC++ 2013 is the current requirement). Last I checked it was working in 2015, would have been a few weeks ago the last time I checked.

CI server says latest build from master was build successfully https://ci.appveyor.com/project/cefsharp/cefsharp/build/55.0.0-CI2059

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Project 'CefSharp.Core' could not be loaded because it's missing install components. To fix this launch Visual Studio setup with the following selections:
Install Visual C++ 2015 Tools for Windows Desktop

That's weird, I do program with C++ here...but I will install those then.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I got it to compile, there was a c++ tool missing indeed.

<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
Expand All @@ -86,6 +88,8 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="Binding\Behaviors\BindHtmlAttachedBehavior.cs" />
<Compile Include="Binding\RelayCommand.cs" />
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
Expand All @@ -94,7 +98,9 @@
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Binding\BindableBase.cs" />
<Compile Include="Binding\TitleConverter.cs" />
<Compile Include="MainViewModel.cs" />
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
Expand Down
37 changes: 37 additions & 0 deletions CefSharp.MinimalExample.Wpf/MainViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using CefSharp.MinimalExample.Wpf.Binding;
using System.Windows.Input;

namespace CefSharp.MinimalExample.Wpf
{
public class MainViewModel : BindableBase
{
private string html;

public MainViewModel()
{
ViewHtmlCommand = new RelayCommand(OnViewHtml);
Address = "www.google.com";
}

public string Html
{
get { return html; }
set { SetProperty(ref html, value); }
}

private string address;

public string Address
{
get { return address; }
set { SetProperty(ref address, value); }
}

public ICommand ViewHtmlCommand { get; private set; }

private void OnViewHtml()
{
Html = "<div dir=\"ltr\"><b><i>test</i></b></div>\r\n";
}
}
}
51 changes: 42 additions & 9 deletions CefSharp.MinimalExample.Wpf/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpf="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behaviors="clr-namespace:CefSharp.MinimalExample.Wpf.Binding.Behaviors"
Title="{Binding Path=Title, ElementName=Browser, Converter={StaticResource TitleConverter}}"
WindowState="Maximized">
<Grid>
Expand All @@ -11,16 +13,47 @@
</Grid.RowDefinitions>
<wpf:ChromiumWebBrowser Grid.Row="0"
x:Name="Browser"
Address="http://www.google.com" />
Address="{Binding Address}" >
<i:Interaction.Behaviors>
<behaviors:LoadHtmlBehavior Html="{Binding Html}" HtmlUrl="http://test/page" />
</i:Interaction.Behaviors>
</wpf:ChromiumWebBrowser>
<StatusBar Grid.Row="1">
<ProgressBar HorizontalAlignment="Right"
IsIndeterminate="{Binding IsLoading, ElementName=Browser}"
Width="100"
Height="16"
Margin="3" />
<Separator />
<!-- TODO: Could show hover link URL here -->
<TextBlock Text="{Binding Address, ElementName=Browser}"/>
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem>
<Button Content="View HTML" Command="{Binding ViewHtmlCommand}" Padding="3,0"/>
</StatusBarItem>
<Separator Grid.Column="1"/>
<StatusBarItem Grid.Column="2">
<ProgressBar HorizontalAlignment="Right"
IsIndeterminate="{Binding IsLoading, ElementName=Browser}"
Width="100"
Height="16"
Margin="3" />
</StatusBarItem>
<Separator Grid.Column="3"/>
<StatusBarItem Grid.Column="4">
<!-- TODO: Could show hover link URL here -->
<TextBlock Text="{Binding Address, ElementName=Browser}" />
</StatusBarItem>
<Separator Grid.Column="5"/>
<StatusBarItem Grid.Column="6" HorizontalContentAlignment="Stretch">
<TextBox Text="{Binding Address, UpdateSourceTrigger=PropertyChanged}" />
</StatusBarItem>
</StatusBar>
</Grid>
</Window>
1 change: 1 addition & 0 deletions CefSharp.MinimalExample.Wpf/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public partial class MainWindow : Window
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
}