Binding to a ViewModel property - binding

Please bear with me, I know very little about WPF so I might be way off target with this...
I currently have a static resource defined as follows:
<XmlDataProvider
x:Key="staticData"
Source="http://someurl/desktopservices/some.xml"
XPath="/menu"/>
This is bound to a Menu control as follows:
<Menu
ItemsSource="{Binding Source={StaticResource staticData}}"
x:Name="MyMenu" />
All is good.
Now I define a view model and create the XmlDataProvider as a propery of the vm:
public class ViewModel : INotifyPropertyChanged
{
private readonly XmlDataProvider dataProvider;
public ViewModel()
{
var document = new XmlDocument();
document.Load("http://someurl/desktopservices/some.xml");
dataProvider = new XmlDataProvider
{
Document = document,
XPath = #"/menu"
};
}
public XmlDataProvider DataProvider
{
get
{
return dataProvider;
}
}
//....
}
The ViewModel is then defined in XAML as another StaticResource:
<local:ViewModel x:Key="vm"/>
How do I change my binding to the DataProvider property of the view model? I would expect:
<Menu
ItemsSource="{Binding DataProvider, Source={StaticResource vm}}"
x:Name="MyMenu" />
(I've tried other combos... this is the one that doesn't error... however nothing gets bound)

Related

Umbraco v9 Call Specific Controller Method and pass parameter

I would like to call specific action method with a parameter and retrieve data from the controller in Umbraco v9.
public class SearchResultController : RenderController
{
private readonly UmbracoHelper UmbracoHelper;
private readonly IPublishedValueFallback PublishedValueFallback;
private ISearchRepository SearchRepository;
public SearchResultController(ILogger<ContentPageController> logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor,
IPublishedValueFallback publishedValueFallback,
UmbracoHelper umbracoHelper, ISearchRepository searchRepo
)
: base(logger, compositeViewEngine, umbracoContextAccessor)
{
UmbracoHelper = umbracoHelper;
PublishedValueFallback = publishedValueFallback;
SearchRepository = searchRepo;
}
[HttpGet]
public override IActionResult Index()
{
var model = new SearchResult(CurrentPage, PublishedValueFallback);
return View("~/Views/SearchResult.cshtml", model);
}
[HttpPost]
public IActionResult SearchResult(string searchString)
{
var results = SearchRepository.SearchString(searchString);
var model = new SearchResult(CurrentPage, PublishedValueFallback);
return View("~/Views/SearchResult.cshtml", model);
}
}
View:
function Search() {
debugger;
var searchString = document.getElementById("searchLabel").value;
var url = "www.test.com/search?searchString=" + searchString;
location.href = url;
}
I have also tried with #Html.Actionlink method, but I really can't make it work to properly redirect and pass the parameter. If i type the link manually and correctly in browser, the value also gets passed into the controller (debugger shows everything is ok).
Thanks for all the Help!
With Umbraco 9 being on dotnet 5+, we now use ViewComponents for this - https://our.umbraco.com/Documentation/Reference/Templating/Mvc/ViewComponents
so for example, you might have a ViewComponent to render your SearchResult list something like this:
using Microsoft.AspNetCore.Mvc;
using MyProject.Core.Models.View;
using MyProject.Core.Services;
namespace MyProject.Core.ViewComponents
{
public class SearchResults: ViewComponent
{
private readonly ISearchRepository searchRepo;
private readonly UmbracoHelper umbraco;
private readonly IPublishedValueFallback publishedValueFallback;
public SearchResults(
UmbracoHelper umbraco,
IPublishedValueFallback publishedValueFallback,
ISearchRepository searchRepo)
{
this.searchRepo = searchRepo;
this.umbraco = umbraco;
this.publishedValueFallback = publishedValueFallback;
}
public IViewComponentResult Invoke(string searchString)
{
var results = SearchRepository.SearchString(searchString);
var model = new SearchResult(umbraco.AssignedContentITem, publishedValueFallback);
return View(model);
}
}
}
Then, create the Default ViewComponent cshtml class in the /Views/Shared/Components/SearchResults directory (named Default.cshtml) something like this:
#inherits UmbracoViewPage<SearchResult>
<!-- Your View code goes here -->
You can render ViewComponents in your templates a couple of different ways, but my favourite is to use the tag-helper format - to do so, register your assembly containing them in your _ViewImports.cshtml file:
#addTagHelper *, MyProject.Core
And then you can you can place them in your templates like so:
<vc:search-results search-string="hello"></vc:search-results>
Microsoft Docs Reference: View components in ASP.NET Core

How to present data with binding and DataTemplate -or- ContentControl for MAUI

How to present a string, number or a also view model with binding and DataTemplate?
I am looking for a MAUI replacement for the WPF ContentControl.
The ContentView has a Content property but this is from type View.
The ContentPresenter has a Content property but this is also from type View. <Ignorable>WTF, Why this is not named ViewPresenter when it can only present a View??? Someoteimes MAUI is weird.</Ignorable>
How to present any content with defining DataTemplates for each data type?
class PropertyViewModel {
public string Name {get;set;}
public object Value {get;set;}
}
<Page.Resources>
<DataTemplate DataType="System.String">
<Entry Text="{Binding}/>
</DataTemplate>
<DataTemplate DataType="System.Int32">
<NumberPicker Value="{Binding}/>
</DataTemplate>
.. more templates, eg. DatePicker for System.DateOnly
</Page.Resources>
<DockLayout>
<Label Text="{Binding Name}
<TemplatedContenView Content={Binding Value}/>
</DockPanel>
The TemplatedContenView or ContentControl (that does not exist in MAUI), can use different templates for different types of Value. In WPF the ContentControl uses ContentTemplate, ContentTemplateSelector or if none specified it looked into the resources to find the template.
<Ignorable>I often have the feeling with MAUI that I have to constantly reinvent things that are standard in WPF. Yes I know MAUI is not WPF, but there should still be at least similar concepts. The switch from WinForms to WPF was much easier and the differences were considerably greater.</Ignorable>
Edit1: a more detailed example
I'm a WPF developer and recently I've started MAUI project. And It looks like you have to reinvent the wheel every time when you are going to write such a simple scenario as you mentioned :(. When you do it using WPF you even don't need to thought about that, it's too easy to implement, but when you use MAUI you should break your mind to do such minor things.
I also encountered the same issue and I didn't find a simple in-box solution. But I came up with the idea to create a control with some layout inside that has attached properties from BindableLayout
TemplatedContentPresenter.xaml.cs:
public partial class TemplatedContentPresenter : ContentView
{
public TemplatedContentPresenter()
{
InitializeComponent();
}
public static readonly BindableProperty DataTemplateSelectorProperty = BindableProperty.Create(nameof(DataTemplateSelector), typeof(DataTemplateSelector), typeof(TemplatedContentPresenter), null, propertyChanged: DataTemplateSelectorChanged);
public static readonly BindableProperty DataTemplateProperty = BindableProperty.Create(nameof(DataTemplate), typeof(DataTemplate), typeof(TemplatedContentPresenter), null, propertyChanged: DataTemplateChanged);
public static readonly BindableProperty DataProperty = BindableProperty.Create(nameof(Data), typeof(object), typeof(TemplatedContentPresenter), null, propertyChanged: DataChanged);
public DataTemplateSelector DataTemplateSelector
{
get =>(DataTemplateSelector)GetValue(DataTemplateSelectorProperty);
set => SetValue(DataTemplateSelectorProperty, value);
}
public DataTemplate DataTemplate
{
get => (DataTemplate)GetValue(DataTemplateProperty);
set => SetValue(DataTemplateProperty, value);
}
public object Data
{
get => GetValue(DataProperty);
set => SetValue(DataProperty, value);
}
private static void DataTemplateSelectorChanged(BindableObject bindable, object oldValue, object newValue)
{
if(bindable is TemplatedContentPresenter contentPresenter && newValue is DataTemplateSelector dataTemplateSelector)
{
BindableLayout.SetItemTemplateSelector(contentPresenter.HostGrid, dataTemplateSelector);
}
}
private static void DataTemplateChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is TemplatedContentPresenter contentPresenter && newValue is DataTemplate dataTemplate)
{
BindableLayout.SetItemTemplate(contentPresenter.HostGrid, dataTemplate);
}
}
private static void DataChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is TemplatedContentPresenter contentPresenter)
{
BindableLayout.SetItemsSource(contentPresenter.HostGrid, new object[] { newValue });
}
}
}
TemplatedContentPresenter.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.TemplatedContentPresenter">
<Grid x:Name="HostGrid" x:FieldModifier="private" />
</ContentView>
Usage:
<Frame WidthRequest="500" HeightRequest="500">
<controls:TemplatedContentPresenter
Data="{Binding}"
DataTemplateSelector="{StaticResource CardTemplateSelector}"/>
</Frame>
UPD:
While I was writing the answer I came up with another solution with a simple converter:
SingleObjectToArray.xaml
internal class SingleObjectToArray : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new object[] { value };
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Usage:
<Frame>
<Frame.Resources>
<converters:SingleObjectToArray x:Key="SingleObjectToArrayConverter"/>
</Frame.Resources>
<Grid BindableLayout.ItemsSource="{Binding Converter={StaticResource SingleObjectToArrayConverter}}"
BindableLayout.ItemTemplateSelector="{StaticResource CardTemplateSelector}" />
</Frame>

TextBlock bind with two sources

I have a TextBlock and to show translated text I use
x:Uid="/FileResourceName/txtControldName" Name="txtControlName"
(in resource file I write: txtControldName.Text = "some text") it works fine. But I would like to hide or show it depend on codebehind object and for that I use
Visibility="{Binding Path = IsMyControlVisible}"
(in that case for text I have to assign some text directly in control like Text="some text"). If I use one of this two properties everything works fine but simultaneously these two properties do not work. Is there any way to do the same?
If I use one of this two properties everything works fine but simultaneously these two properties do not work. Is there any way to do the same?
It's not a normal behavior. There is no conflict between binding to Visibility property and setting text in resource file. Have you set DataContext for your Binding?
Please see the following code sample, it worked well.
<Grid>
<TextBlock x:Uid="txtControldName" Visibility="{Binding IsMyControlVisible}"></TextBlock>
<Button Content="test" Click="Button_Click"></Button>
</Grid>
public sealed partial class MainPage : Page,INotifyPropertyChanged
{
private Visibility _IsMyControlVisible;
public Visibility IsMyControlVisible
{
get { return _IsMyControlVisible; }
set
{
_IsMyControlVisible = value;
RaisePropertyChange("IsMyControlVisible");
}
}
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChange(String PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,new PropertyChangedEventArgs(PropertyName));
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
IsMyControlVisible = IsMyControlVisible == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed;
}
}
Please note that you need to implement the INotifyPropertyChanged interface, when the property value is change, it will notify the UI.

How to use Autofac to inject ViewModels in a Windows Phone 8 application?

I want to be able to inject ViewModels into the Views in my Windows Phone 8 application using Autofac as my IoC container. How do I go about doing this? I have looked at the Caliburn.Micro framework, but I'd like to use something simpler.
Precisely for this purpose I have created a small demo application. It defines a ViewModelLocator class:
public class ViewModelLocator
{
private readonly IContainer container;
public ViewModelLocator()
{
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<MainViewModel>();
containerBuilder.RegisterType<ItemViewModel>();
this.container = containerBuilder.Build();
}
public MainViewModel MainViewModel
{
get
{
return this.container.Resolve<MainViewModel>();
}
}
public ItemViewModel ItemViewModel
{
get
{
return this.container.Resolve<ItemViewModel>();
}
}
}
To use this class from your views, you have to add it to your application's resources. You do this by modifying the Application.Resources section in App.xaml:
<Application.Resources>
<local:ViewModelLocator xmlns:local="clr-namespace:AutofacWP8DependencyInjectionDemo" x:Key="ViewModelLocator"/>
</Application.Resources>
Now you will be able to inject the view model in the view. Just have the view point to the DataContext. To reference the MainViewModel as the DataContext simply add the following to your view:
DataContext="{Binding Path=MainViewModel, Source={StaticResource ViewModelLocator}}"
You can see that it sets the DataContext to the MainViewModel property of the ViewModelLocator class, which uses Autofac to create the MainViewModel instance using dependency injection.
You can find the source here: https://github.com/ErikSchierboom/autofacwp8dependencyinjectiondemo.git

RenderPartial() in T4 Template

I am wondering if its possible to take an existing partial view's logic and reuse it for dynamic email generation, within a preprocessor template?
When looking through the T4ToolKit's intellisense options for
<## import namespace="System.Web.Mvc" #>
Mvc's namespace does not appear, is it possible to include the namespace and call
Html.RenderPartial("viewName", this.Model)
from within a preprocessor template?
i.e.
<## template language="C#" #>
This is a header
<#= Html.RenderPartial("<%PATH%>/MyPartialRazerView", this.Model) #>
This is a Footer
<#+
public MyType Model { get; set; }
#>
so I may programmatically access my template, reuse a view's display logic and build, say an email on the fly (I know the Email line is nonsense, just short hand for simplicity)
var template = MyTemplate(){ Model = MyViewModel };
Email.Send(emailAddress, title, template.TransformText(), null) etc..
TIA
This can be done for sure, but you need to use a different extension methods than RenderPartial, because these write directly to the response. I tried Partial extension methods, which return the MvcHtmlString which works fine in the template. This is my test T4 runtime template, RuntimeTextTemplate1.tt:
<## template language="C#" #>
<## import namespace="System.Web.Mvc.Html" #>
LogOnPartial:
<#= Html.Partial("_LogOnPartial") #>
Then you also need to jump some ASP.NET MVC hoops to get the actual HtmlHelper instance into your template.
I created a partial class, to add the Html property to the template and instantiate the HtmlHelper and to provide a constructor:
public partial class RuntimeTextTemplate1
{
public HtmlHelper Html
{
get;
private set;
}
public RuntimeTextTemplate1(ViewContext viewContext, IViewDataContainer viewDataContainer)
{
Html = new HtmlHelper(viewContext, viewDataContainer);
}
}
A HtmlHelper needs a ViewContext and IViewDataContainer to be created and those in turn have another dependencies. I provided what is needed from the controller using some dummy classes:
public class HomeController : Controller
{
public ActionResult TemplateTest()
{
var viewContext = new ViewContext(ControllerContext, new DummyView(), ViewData, TempData, TextWriter.Null);
var template = new RuntimeTextTemplate1(viewContext, new ControllerViewDataContainer(this));
return Content(template.TransformText());
}
}
public class DummyView : IView
{
public void Render(ViewContext viewContext, TextWriter writer)
{
// Do nothing
}
}
public class ControllerViewDataContainer : IViewDataContainer
{
private Controller controller;
public ViewDataDictionary ViewData
{
get { return controller.ViewData; }
set { }
}
public ControllerViewDataContainer(Controller controller)
{
this.controller = controller;
}
}
And I successfully get the template output out of it.
So while this can be done, it depends on your specific situation, how exactly you need to use it, how your view is parametrized, and how you can assemble the required classes together to get to the HtmlHelper instance.
In the end, you may find that making the template the primary source of the required output, and using this in your views and outside them is easier than the other way around.

Resources