load URL into WebBrowser with MVVM - c#

I'm makeing an app where you have a listbox in the buttom with a logo of different websites, and a WebBrowseron the top. The idea is that when you press on a logo, the webBrowser load the corresponding page. I already make this work, but I want to remake the app with MVVM to make it better. I've made the listbox with all the logos, but I dont know how to load the URL onto the WebBrowser when I click on the logo.

Not 100% sure if this will work on Phone7 but worth a shot...
Fist off the WebBrowser Source property is not bindable as its not a DependancyProperty so you will have to make a helper class to create an AttachedProperty to help with the binding.
Then you can link your ListBox SelectedItem to the new LinkSource property using the Property that contains the actual link from your ListBoxItem.
Example:
Xaml:
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication8"
Title="MainWindow" Height="233" Width="405" Name="UI">
<StackPanel Orientation="Horizontal" DataContext="{Binding ElementName=UI}">
<ListBox x:Name="listbox" ItemsSource="{Binding Links}" Width="100" DisplayMemberPath="Name"/>
<WebBrowser local:WebBrowserHelper.LinkSource="{Binding ElementName=listbox, Path=SelectedItem.Site}" Width="200"/>
</StackPanel>
</Window>
Code:
public partial class MainWindow : Window
{
private ObservableCollection<Link> _links = new ObservableCollection<Link>();
public MainWindow()
{
InitializeComponent();
Links.Add(new Link { Name = "StackOverflow", Site = new Uri("http://stackoverflow.com/") });
Links.Add(new Link { Name = "Google", Site = new Uri("http://www.google.com/") });
}
public ObservableCollection<Link> Links
{
get { return _links; }
set { _links = value; }
}
}
// ListBox item
public class Link
{
public string Name { get; set; }
public Uri Site { get; set; }
}
// helper calss to create AttachedProperty
public static class WebBrowserHelper
{
public static readonly DependencyProperty LinkSourceProperty =
DependencyProperty.RegisterAttached("LinkSource", typeof(string), typeof(WebBrowserHelper), new UIPropertyMetadata(null, LinkSourcePropertyChanged));
public static string GetLinkSource(DependencyObject obj)
{
return (string)obj.GetValue(LinkSourceProperty);
}
public static void SetLinkSource(DependencyObject obj, string value)
{
obj.SetValue(LinkSourceProperty, value);
}
// When link changed navigate to site.
public static void LinkSourcePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var browser = o as WebBrowser;
if (browser != null)
{
string uri = e.NewValue as string;
browser.Source = uri != null ? new Uri(uri) : null;
}
}
}
Result:

Your question are actually two questions.
There're several ways to pick up clicks from listbox. Most basic one is <Listbox SelectedItem="{Binding selectedItem,mode=TwoWay}" ...
To set the web browser's URL, you could implement INotifyPropertyChanged in your VM, declare public Uri browserUri { get; private set; } in your WM, be sure to raise PropertyChanged when you change the property, and in XAML <WebBrowser Source="{Binding browserUri}" />

Related

How can I render HTML or markdown in a WPF datagrid?

Is there a way to render/show HTML (or Markdown) in a .NET Core WPF Datagrid column / cell ?
I have some text in a database which I would like to add some formatting options to.
I was thinking to add the possibility to format it using markdown.
This text then is displayed in a datagrid using itemsource = List<myobject>, and I would like to have it displayed formatted in the datagrid column/cell.
So I used Markdig to generate HTML from the text in the database. This gives me something like:
<p>This is a text with some <em>emphasis</em></p>
And this should be displayed like:
This is a text with some emphasis
But then I get stuck. It seems like HTML is something alien to WPF.
It's possible to do this with WPF's WebBrowser control in a DataGridTemplateColumn. Some sample code is below.
To get the WebBrowser to display an HTML string you have to call NavigateToString. I've done this using an attached property as described in another Stack Overflow answer.
I've also done some work to remove the disabled scrollbars that the control will put in a cell by default, even if the content fits. I've used CSS styling to do this, but it's a little hacky.
Note that we are warned that the WebBrowser control is quite heavyweight in terms of memory usage, so you probably wouldn't want to do it this way in a grid with a lot of rows.
C#:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace WpfApp14
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.CreateProducts();
this.MyDataGrid.ItemsSource = Products;
}
public ObservableCollection<Product> Products { get; set; }
private void CreateProducts()
{
Products = new ObservableCollection<Product>
{
new Product{ID = 1, Html = "HTML with no formatting"},
new Product{ID = 2, Html= "<h1>This is a <i>test</i> header</h1>"},
new Product{ID = 3, Html="<p>This is a text with some <em>emphasis</em></p>"},
new Product{ID = 4, Html="<ul><li>Product 1</li><li>Product 2</li><li>Product 3</li></ul>"}
};
}
}
public class Product : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int id;
public int ID
{
get { return id; }
set
{
id = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(ID)));
}
}
private string html;
public string Html
{
get { return html; }
set
{
html = RemoveScrollbars(value);
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(Html)));
}
}
private string RemoveScrollbars(string html)
{
string before = "<head><style>body{overflow:hidden;}</style></head><body>";
string after = "</body>";
return before + html + after;
}
}
public static class BrowserBehavior
{
public static readonly DependencyProperty HtmlProperty = DependencyProperty.RegisterAttached(
"Html",
typeof(string),
typeof(BrowserBehavior),
new FrameworkPropertyMetadata(OnHtmlChanged));
[AttachedPropertyBrowsableForType(typeof(WebBrowser))]
public static string GetHtml(WebBrowser d)
{
return (string)d.GetValue(HtmlProperty);
}
public static void SetHtml(WebBrowser d, string value)
{
d.SetValue(HtmlProperty, value);
}
static void OnHtmlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
WebBrowser wb = d as WebBrowser;
if (wb != null)
wb.NavigateToString(e.NewValue as string);
}
}
}
XAML:
<Window x:Class="WpfApp14.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:WpfApp14"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<DataGrid x:Name="MyDataGrid" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding ID}"/>
<DataGridTemplateColumn Width="300">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<WebBrowser local:BrowserBehavior.Html="{Binding Html}" Height="90"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Window>

On NavigationView page change, data binding is lost

Changing page through the NavigationView control causes some data bindings to fail/be lost.
data model:
public class Flashpoint
{
private string name;
private string description;
private string image;
public string Name
{
get { return name; }
set { name = value; }
}
public string Description
{
get { return description; }
set { description = value; }
}
public string Image
{
get { return image; }
set { image = value; }
}
}
page xaml:
<Page
x:Class="Braytech_3.Views.MainPage"
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="using:Braytech_3.Views"
xmlns:data="using:Braytech_3.Models"
Style="{StaticResource PageStyle}"
mc:Ignorable="d">
<Grid x:Name="ContentArea">
<StackPanel Height="400" HorizontalAlignment="Left" VerticalAlignment="Top">
<Image HorizontalAlignment="Center" VerticalAlignment="Center" Source="{x:Bind CurrentFlashpoint.Image, Mode=OneWay}" Stretch="UniformToFill"/>
<TextBlock Text="{x:Bind CurrentFlashpoint.Name, Mode=OneWay}" />
</StackPanel>
page cs:
namespace Braytech_3.Views
{
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
Flashpoint CurrentFlashpoint { get; set; }
public MainPage()
{
InitializeComponent();
Render();
}
public event PropertyChangedEventHandler PropertyChanged;
private void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
private void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private async void Render()
{
StorageFolder tempFolder = ApplicationData.Current.TemporaryFolder;
StorageFile APIData = await tempFolder.GetFileAsync("APIData.json");
string json = await FileIO.ReadTextAsync(APIData);
Universal request = JsonConvert.DeserializeObject<Universal>(json);
foreach (var challenge in request.response.data.challenges)
{
if (challenge.type == "flashpoint")
{
CurrentFlashpoint = new Models.Flashpoint
{
Name = challenge.name,
Description = challenge.description,
Image = $"/Assets/Images/{ challenge.slug }.jpg"
};
}
}
}
}
}
Omitted from the above code excerpts is an ObservableCollection that is bound to the pivot control below the image control. It works as expected. Vendors page also uses an ObservableCollection. As I am only trying to bind single items (textblock, image), I wasn't sure how I could use an ObservableCollection and if I even should attempt to as to me it's the equivalent of an array of items where as I'm trying to bind a single item.
Using the live tree inspector I was able to inspect the image control, before and after the data binding is lost, but it didn't lead me to an answer as to why.
Why does it lose its binding and how can I fix it?
The better way is that you could set NavigationCacheMode="Required" for MainPage to avoid repeat loading.
The page is cached and the cached instance is reused for every visit regardless of the cache size for the frame.
This is official document that you could refer.

MVVM Dynamically add fields into View

I'm developing a WPF application using caliburn.micro MVVM framework..
In-order to develop a search screen, I need to dynamically load fields into the view, based on model properties.
Consider below view and view model:
SearchViewModel
SearchView
Let's assume T is a type of Product in below example.
public class SearchViewModel<T>
{
public T Item{get;set;}
}
public class Product
{
public int Id{get;set;}
public string Name{get;set;}
public string Description{get;set;}
}
I have a user control called SearchView.xaml with no contents on it.
Whenever View is loaded new fields should be added to the view and field should be bound to the properties.
According to above code example, there are 3 public properties in the Product class, therefore 3 TextBoxes should be added to the view dynamically. When user enters data in the text field, corresponding property should be updated.
Is this possible?
Can any experts help me to achieve this by providing some examples?
I would propose going about this differently. Instead of thinking about dynamically adding properties to a view / model, I would think about adding information about those properties to a list on the viewmodel. That list would then be bound to an ItemsControl with a template that looks like a TextBox.
So your view-model would have a property on it for the "thing" you want to examine. In the setter for this property, use reflection to enumerate the properties you are interested in, and add an instance of some kind of FieldInfo class (that you create) to the list of properties with the binding.
This has the benefit of keeping everything all MVVM compatible too, and there is no need to dynamically create controls with your own code.
The example below uses my own MVVM library (as a nuget package) rather than caliburn.micro, but it should be similar enough to follow the basic idea. The full source code of the example can be downloaded from this BitBucket repo.
As you can see in the included screenshots, the search fields are created dynamically on the view without any code in the view. Everything is done on the viewmodel. This also gives you easy access to the data that the user enters.
The view-model:
namespace DynamicViewExample
{
class MainWindowVm : ViewModel
{
public MainWindowVm()
{
Fields = new ObservableCollection<SearchFieldInfo>();
SearchableTypes = new ObservableCollection<Type>()
{
typeof(Models.User),
typeof(Models.Widget)
};
SearchType = SearchableTypes.First();
}
public ObservableCollection<Type> SearchableTypes { get; }
public ObservableCollection<SearchFieldInfo> Fields { get; }
private Type _searchType;
public Type SearchType
{
get { return _searchType; }
set
{
_searchType = value;
Fields.Clear();
foreach (PropertyInfo prop in _searchType.GetProperties())
{
var searchField = new SearchFieldInfo(prop.Name);
Fields.Add(searchField);
}
}
}
private ICommand _searchCommand;
public ICommand SearchCommand
{
get { return _searchCommand ?? (_searchCommand = new SimpleCommand((obj) =>
{
WindowManager.ShowMessage(String.Join(", ", Fields.Select(f => $"{f.Name}: {f.Value}")));
})); }
}
}
}
The SearchFieldInfo class:
namespace DynamicViewExample
{
public class SearchFieldInfo
{
public SearchFieldInfo(string name)
{
Name = name;
}
public string Name { get; }
public string Value { get; set; } = "";
}
}
The view:
<Window
x:Class="DynamicViewExample.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:local="clr-namespace:DynamicViewExample"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
d:DataContext="{d:DesignInstance local:MainWindowVm}"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ComboBox
Grid.Row="0"
ItemsSource="{Binding Path=SearchableTypes}"
SelectedItem="{Binding Path=SearchType}" />
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=Fields}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBox Width="300" Text="{Binding Path=Value}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Grid.Row="2" Command="{Binding Path=SearchCommand}">Search</Button>
</Grid>
</Window>
The model classes:
class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneNumber { get; set; }
public string Id { get; set; }
}
class Widget
{
public string ModelNumber { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Here is a basic example of how you could generate a TextBox per public property of the T in the control using reflection.
SearchView.xaml:
<Window x:Class="WpfApplication4.SearchView"
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:WpfApplication4"
mc:Ignorable="d"
Title="SearchView" Height="300" Width="300">
<StackPanel x:Name="rootPanel">
</StackPanel>
</Window>
SearchView.xaml.cs:
public partial class SearchView : UserControl
{
public SearchView()
{
InitializeComponent();
DataContextChanged += SearchView_DataContextChanged;
DataContext = new SearchViewModel<Product>();
}
private void SearchView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
Type genericType = e.NewValue.GetType();
//check the DataContext was set to a SearchViewModel<T>
if (genericType.GetGenericTypeDefinition() == typeof(SearchViewModel<>))
{
//...and create a TextBox for each property of the type T
Type type = genericType.GetGenericArguments()[0];
var properties = type.GetProperties();
foreach(var property in properties)
{
TextBox textBox = new TextBox();
Binding binding = new Binding(property.Name);
if (!property.CanWrite)
binding.Mode = BindingMode.OneWay;
textBox.SetBinding(TextBox.TextProperty, binding);
rootPanel.Children.Add(textBox);
}
}
}
}
}
The other option will obviously be to create a "static" view for each type of T and define the TextBox elements in the XAML markup as usual.

How to delete a ListView Item by a function in UserControl Template?

I am Developing windows 10 Universal app with c#.
I have a UserControl that is the MyListview item template. Listview will Bind the data. In userControl,there is a button for Delete the usercontrol DependencyProperty Content(contain string Text, Name and int Id ).
Listview show the text of object and the button for remove it.
Now how can remove that item from my List by click on remove Button?
Update
my Data class:
class Data
{
public int Id { get; set; }
public string Text { get; set; }
}
my usercontrol.cs :
public Data Content
{
get { return (Data)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
// Using a DependencyProperty as the backing store for Content. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(Data), typeof(MyUserControl1), new PropertyMetadata(null));
usercontrol xaml:
<StackPanel>
<TextBlock x:Name="textBlock" Text="{Binding Content.Text, ElementName=textBlock}" />
<Button Click="Remove_Click"/>
</StackPanel>
my list implementing:
<Page.Resources>
<DataTemplate x:Key="ListViewTemplate">
<local:MyUserControl1 Content="{Binding}"/>
</DataTemplate>
</Page.Resources>
<Grid>
<ListView x:Name="ListView" ItemTemplate="{StaticResource ListViewTemplate}" />
</Grid>
and in the code behinde the Page I use an ObservableCollection<Data> items = new ObservableCollection<Data>();to set Listview.ItemsSource to it.
The main Problem is How to remove that item from the items in MyUsercontrol1
You wrote about binding so I'm assuming that in your XAML there is a following code or similar:
<ListView ItemSource = "{Bind SomeCollection"} ... />
If I'm right there is no much to do. If SomeCollection is of type ObservableCollection<T> it is enough to remove an item from SomeCollection and UI will be refreshed ''automatically''. To sum up:
Declare SomeCollection as ObservableCollection<T>.
In a command that is executed when Delete button is clicked (or in an event handler) simply call ObservableCollection<T>.Remove.
UPDATE
This code is not elegant but shows an idea. Firstly we need to modify Data class:
public class Data
{
public int Id { get; set; }
public string Text { get; set; }
public Action<Data> OnRemoveCallback { get; set; }
public void OnRemove()
{
OnRemoveCallback(this);
}
}
OnRemoveCallback will be used to inform ListView that a given data element should be removed. Remove_click handler in MyUserControl simply executes OnRemove:
private void Remove_Click(object sender, RoutedEventArgs e)
{
Content.OnRemove();
}
Finally, in the code behind of your Page we have to define a logic that will be responsible for actual removing data items from the list:
public void Remove(Data d)
{
((ObservableCollection<Data>) ListView.ItemsSource).Remove(d);
}
...
ListView.ItemsSource = new ObservableCollection<Data>()
{
new Data() {Id = 1, Text = "1", OnRemoveCallback = Remove},
new Data() {Id = 2, Text = "2", OnRemoveCallback = Remove}
};
Now your Page will be informed whenever Delete button is pressed and will do a job.
As I said it is not a perfect solution. Personally, I'll use MVVM pattern. Thanks do that XAML and C# will be seperated.

How to bind a single object instance in WPF?

I am a WPF newcomer, and I've been searching for two days with no luck. I have a WPF window that has several text box controls, and a single object with some properties. This object is passed to the codebehind of my WPF window in it's constructor:
public partial class SettingsDialog : Window
{
public SettingsObject AppSettings
{
get;
set;
}
public SettingsDialog(SettingsObject settings)
{
this.AppSettings = settings;
InitializeComponent();
}
}
The SettingsObject looks something like this (simplified for clarity):
public class SettingsObject
{
public string Setting1 { get; set; }
public string Setting2 { get; set; }
public string Setting3 { get; set; }
public SettingsObject()
{
this.Setting1 = "ABC";
this.Setting2 = "DEF";
this.Setting3 = "GHI";
}
}
And my WPF window (simplified):
<Window x:Class="MyProgram.SettingsDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding Source=AppSettings}">
<Grid>
<TextBox Name="Setting1Textbox" Text="{Binding Path=Setting1}"></TextBox>
<TextBox Name="Setting2Textbox" Text="{Binding Path=Setting2}"></TextBox>
<TextBox Name="Setting3Textbox" Text="{Binding Path=Setting3}"></TextBox>
</Grid>
</Window>
How do you acheive two-way binding in this situation? I've tried what you see above (and so much more) but nothing works!
Have you set the DataContext property of the window to your instance of AppSettings?
public SettingsDialog(SettingsObject settings)
{
InitializeComponent();
//While this line should work above InitializeComponent,
// it's a good idea to put your code afterwards.
this.AppSettings = settings;
//This hooks up the windows data source to your object.
this.DataContext = settings;
}

Categories

Resources