Programatically add ValidationRules to WPF DataGrid when autogenerating columns - wpfdatagrid

I want to do this in the AutoGeneratingColumn event:
<my:DataGridTextColumn Header="CompanyName">
<my:DataGridTextColumn.Binding>
<Binding Path="CompanyName">
<Binding.ValidationRules>
<local:DataRowValidation ValidationStep="UpdatedValue" />
</Binding.ValidationRules>
</Binding>
</my:DataGridTextColumn.Binding>
</my:DataGridTextColumn>
Is it possible? The columns are autogenerated so I just want to add the ValidationRules to Binding.

I ended up with this, if no one has any better solution.
private void DataGridAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var binding = (Binding)((DataGridBoundColumn)e.Column).Binding;
binding.ValidationRules.Add(new DataRowValidationRule { ValidationStep = ValidationStep.UpdatedValue });
}

Related

WinUI notifications: COM exception is raised when instanciating instance of "AppNotification"

I wanted to include notifications in my existing WinUI 3 application which uses Windows App SDK 1.1.4 and .NET 6 (The application does not and shall not use packaging / MSIX).
In order to achieve this, I tried to extract some code of an example application that I created with the "Template studio for WinUI" project template (assistant), see https://github.com/microsoft/TemplateStudio/ (The sample application also works with the 'unpackaged' deployment model).
The code which I extracted from the example application looks like this (the relevant parts should be the methods 'Initialize' and 'Show'):
public class AppNotificationService : IAppNotificationService
{
public AppNotificationService()
{
}
~AppNotificationService()
{
Unregister();
}
public void Initialize()
{
AppNotificationManager.Default.NotificationInvoked += OnNotificationInvoked;
AppNotificationManager.Default.Register();
}
public void OnNotificationInvoked(AppNotificationManager sender, AppNotificationActivatedEventArgs args)
{
// TODO: Handle notification invocations when your app is already running.
//// // Navigate to a specific page based on the notification arguments.
//// if (ParseArguments(args.Argument)["action"] == "Settings")
//// {
//// App.MainWindow.DispatcherQueue.TryEnqueue(() =>
//// {
//// _navigationService.NavigateTo(typeof(SettingsViewModel).FullName!);
//// });
//// }
App.MainWindow.DispatcherQueue.TryEnqueue(() =>
{
App.MainWindow.ShowMessageDialogAsync("TODO: Handle notification invocations when your app is already running.", "Notification Invoked");
App.MainWindow.BringToFront();
});
}
// EXCEPTION IN THIS METHOD
public bool Show(string payload)
{
var appNotification = new AppNotification(payload); // COM EXCEPTION HERE
AppNotificationManager.Default.Show(appNotification);
return appNotification.Id != 0;
}
public NameValueCollection ParseArguments(string arguments)
{
return HttpUtility.ParseQueryString(arguments);
}
public void Unregister()
{
AppNotificationManager.Default.Unregister();
}
}
As you can see, the code contains a method "Show" that has a string for the payload that represents the notification message. In addition there is a "Initialize" method that the example code calls upon application startup.
In order to call the "Show" method of the code above, I created some small event handler in my application that gets called when I click a button:
private void CreateNotification_Click(object sender, RoutedEventArgs e)
{
AppNotificationService notificationService = new AppNotificationService();
notificationService.Initialize();
string notificationContent = "test";
notificationService.Show(notificationContent);
}
However, the call to "notificationService.Show(notificationContent);" always causes a ComException "0xC00CE556" that is raised when the code tries to instanciate the AppNotification instance see here:
I do not know what I am missing here. It seems that the template studio application does something additional to get the notification working, that I am currently not doing in my code. But I have no idea what that is. Any suggestions?
I couldn't reproduce your COM Exception but these steps worked.
Create a simple WinUI 3 app project.
Bring AppNotificationService.cs and IAppNotificationService.cs from a TemplateStudio project with app notifications.
Open Package.appxmanifest using a text editor (VSCode).
Add these namespaces:
<Package
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10">
</Package>
Declare these Extensions inside Applications:
<Applications>
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
<uap:VisualElements DisplayName="WinUI3BlankAppProjectTemplate" Description="WinUI3BlankAppProjectTemplate" BackgroundColor="transparent" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
<Extensions>
<!--Specify which CLSID to activate when notification is clicked-->
<desktop:Extension Category="windows.toastNotificationActivation">
<desktop:ToastNotificationActivation ToastActivatorCLSID="12345678-9ABC-DEFG-HIJK-LMNOPQRSTUVW" />
</desktop:Extension>
<!--Register COM CLSID-->
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="AppNotifications.exe" Arguments="----AppNotificationActivated:" DisplayName="Toast activator">
<com:Class Id="12345678-9ABC-DEFG-HIJK-LMNOPQRSTUVW" DisplayName="Toast activator" />
</com:ExeServer>
</com:ComServer>
</com:Extension>
</Extensions>
</Application>
</Applications>
Replace Executable="AppNotifications.exe" with your app name.
Create a GUID from [Tools]-[Create GUID] in VisualStudio menu.
Replace the two GUIDs in the Extensions with the GUID you created.
Save the Package.appxmanifest file and reopen and rebuild the solution.
Call the Show method passing a valid payload. For example:
var xmlPayload =
#"
<toast launch=""action=ToastClick"">
<visual>
<binding template=""ToastGeneric"">
<text>App Notification</text>
<text></text>
</binding>
</visual>
<actions>
<action content=""Settings"" arguments=""action=Settings""/>
</actions>
</toast>
";
appNotificationService.Show(xmlPayload);
UPDATE
For un-packaged(non-packaged) apps you get a COM Exception if you don't call the Initialize() method. So, the step 10. should be something like this:
Call Initialize then Show method passing a valid payload. For example:
AppNotificationService appNotificationService = new();
appNotificationService.Initialize();
var xmlPayload =
#"
<toast launch=""action=ToastClick"">
<visual>
<binding template=""ToastGeneric"">
<text>App Notification</text>
<text></text>
</binding>
</visual>
<actions>
<action content=""Settings"" arguments=""action=Settings""/>
</actions>
</toast>
";
appNotificationService.Show(xmlPayload);
I found out what the problem was in my case.
I turned out, that the problem was related to the string I used as notification content. This must not be an arbitrary string (like "test" in the example I used in the question), but an xml string that has a specific format which is needed to represent a "toast" message.
This xml string for the toast message can contain specific elements for text, images and buttons that may appear in the message.
Simple example:
<?xml version="1.0" encoding="utf-8"?>
<toast>
<visual>
<binding template="ToastGeneric">
<text>Some text</text>
</binding>
</visual>
</toast>
This article shows an example of the possible syntax of this xml string:
Quickstart: App notifications in the Windows App SDK - 4 Display an app notification
There are also classes and helpers for the construction of the xml string that you can use by installing the "CommunityToolkit.WinUI.Notifications"
Nuget package:
ToastContent class
ToastContentBuilder class
Example code
Basically, all you need to do to create a toast notification is this:
private void CreateNotification_Click(object sender, RoutedEventArgs e)
{
// Version 1: Directly define the xmlPayload string:
// string xmlPayload = #"<?xml version=""1.0"" encoding=""utf-8""?><toast><visual><binding template=""ToastGeneric""><text>Some text</text></binding></visual></toast>";
// Version 2: Create the the xmlPayload string using the ToastContentBuilder
// ToastContentBuilder comes with the "CommunityToolkit.WinUI.Notifications" Nuget package
ToastContent toastContent = new ToastContentBuilder()
.AddText("Some text")
.GetToastContent();
string xmlPayload = toastContent.GetContent();
var toast = new AppNotification(xmlPayload);
AppNotificationManager.Default.Show(toast);
}
In the example code, I have two versions on how to create the xml string that represents the toast message. You can create the xml yourself or use the ToastContentBuilder class from the "CommunityToolkit.WinUI.Notifications" Nuget package.
Personal opinion
I know that StackOverflow does no focus on personal opinions. However, I would like to express that I am quite disappointed to see that a code like
new AppNotification("test");
raises an ComException lacking any useful information instead of an meaningful exception that contains some hint for the developer complaining about the incorrect format of the provided xml string.

Updating a property in a viewmodel of popup doesn't update the UI

As in the title I have a problem where updating a property in a viewmodel of popup doesn't update the UI. I use popups from xamarin community toolkit. I'm using a command that does this task:
async Task ShowPopup()
{
MessagingCenter.Send(AnimeGroupObservable, "AnimeGroups");
Shell.Current.ShowPopup(new MediaListGroupsPopup());
}
It sends a message with payload and shows popup. This is popup viewmodel:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Input;
using OtakuApp.Models;
using Xamarin.Forms;
namespace OtakuApp.ViewModels
{
class MediaListGroupsPopupViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public ObservableCollection<Group> _AnimeGroups = new ObservableCollection<Group>();
public ObservableCollection<Group> AnimeGroups
{
get => _AnimeGroups;
set
{
if (_AnimeGroups == value)
return;
_AnimeGroups = value;
OnPropertyChanged();
}
}
public String _label;
public String label
{
get => _label;
set
{
if (value == _label)
return;
_label = value;
OnPropertyChanged();
}
}
public MediaListGroupsPopupViewModel()
{
MessagingCenter.Subscribe<ObservableCollection<Group>>(this, "AnimeGroups", (AnimeGroupObservable) =>
{
Console.WriteLine(AnimeGroupObservable[0].Name);
label = AnimeGroupObservable[1].Name;
MessagingCenter.Unsubscribe<ObservableCollection<Group>>(this, "AnimeGroups");
});
}
}
}
I'm planning on having a small collection view of labels to select from. But right now I'm struggling to update one label just for testing purposes, so you can imagine that I've tried collection view and it didn't work. Setting _label to something manually in the code shows that binding works. It's just not updating for some reason.
Popup xaml file:
<?xml version="1.0" encoding="utf-8" ?>
<xct:Popup
x:Class="OtakuApp.Popups.MediaListGroupsPopup"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
Size="300,300">
<StackLayout>
<Label Text="{Binding label}" />
</StackLayout>
</xct:Popup>
So right now I have two problems:
Label doesn't update. It's binded to a property that has INotifyPropertyChanged
Weirdly this subscription happens only the second time (and after that too, just not the first time) I open up a popup. Is this because it's in the constructor? If yes, what's the correct way to deal with it?
Also a small question - I have unsubscribe at the end of subscription. When I didn't have it and I printed out AnimeGroupObservable[0].Name, the first time it was printed one time, the second time I open up the popup two times etc. Is the unsubscribe at the end the correct way to fix this?
since you are passing a single parameter to a single page, using the constructor would be much simpler than MessagingCenter (which is great, but overkill for this scenario)
when creating the page, pass the parameter in the constructor
Shell.Current.ShowPopup(new MediaListGroupsPopup(AnimeGroupObservable));
then modify the page constructor to accept the parameter
public MediaListGroupsPopup(ObservableCollection<Group> groups)
{
// you did't show how you create your VM, but I assume it's something like this
this.BindingContext = new MediaListGroupsPopupViewModel(groups);
}
then modify your VM constructor
public MediaListGroupsPopupViewModel(ObservableCollection<Group> groups)
{
label = groups[1].Name;
}
if you really are only using a single string value, you could just pass that instead of the entire ObservableCollection

Set WPF Combobox selected item from property setter

I am using WPF with databinding. I have a Combobox bound to a list of strings. I want the selected item in the list to set a field in my View Model. However, I sometimes want to override the user's selection and re-set the selected value in the Combobox but I don't seem to be able to do that.
Here's the View Model code:
public class SettingsViewModel : INotifyPropertyChanged
{
public enum RateTypes
{
[Description("128Hz")]
Hz128 = 4,
[Description("256Hz")]
Hz256 = 6,
[Description("400Hz")]
Hz400 = 7,
[Description("512Hz")]
Hz512 = 8,
[Description("600Hz")]
Hz600 = 9
}
RateTypes m_SelectedRate;
List<string> RateOptions = ((RateTypes [])Enum.GetValues(typeof(RateTypes)))
.Select(o => o.Description())
.ToList();
public string SelectedRate
{
get {return m_SelectedRate.Description();}
set
{
if (value == RateType.Hz256)
{
MessageBox.Show("256Hz not an option with your system");
m_SelectedRate= IMURate.Hz400;
}
else
{
m_SelectedRate = value;
}
OnPropertyChanged(nameof(SelectedRate));
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyChanged)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyChanged);
handler(this, e);
}
}
}
and the XAML has:
<ComboBox Grid.SelectedItem="{Binding SelectedRate, Mode=TwoWay}" ItemsSource="{Binding RateOptions}">
However, when I select 256Hz in the GUI, the value displayed stays as 256Hz instead of changing to 400Hz. If I call OnPropertyChanged(SelectedRate) from a separate function, the value does change.
I've tried using SelectedValue and UpdateSourceTrigger but can't find anything that works.
Any ideas?
Unbelieveable. I spent hours searching for an answer before posting that question but then 10 minutes after posting, I thought of a new search term that led me to the answer.
I simply needed to add IsAsync="true" to SelectedValue in the XAML:
<ComboBox Grid.SelectedItem="{Binding SelectedRate, Mode=TwoWay, IsAsync="true"}" ItemsSource="{Binding RateOptions}">
Oh well, hopefully this will help someone else.
Add Delay=1 fixed the problem for me. The IsAsync=true approach worked, too, but it seems to update the combobox slower sometimes and it created a bug in my GUI where changing the Combobox value in the gui doesnt work once after App start up.
<ComboBox Grid.SelectedItem="{Binding SelectedRate, Mode=TwoWay, Delay=1}" ItemsSource="{Binding RateOptions}">

TagMapping the AudienceEditor control not working

I needed to add text to the AudienceEditor Control everywhere and so I created a custom
class inheriting from `Microsoft.Office.Server.WebControls.AudienceEditor` class
public class AudienceEditorText : Microsoft.Office.Server.WebControls.AudienceEditor
{
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
Label lblText = new Label();
lblText.ID = "Note";
lblText.Visible = true;
lblText.Text = "Text Needs to be changed";
lblText.Attributes.Add("style", "color: red");
Controls.Add(lblText);
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
}
protected override void CreateChildControls()
{
base.CreateChildControls();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
}
}
and added the following tags in web.config
<tagMapping>
<add tagType="Microsoft.Office.Server.WebControls.AudienceEditor, Microsoft.Office.Server, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" mappedTagType="Hub.AudienceEditorProject.AudienceEditorText, Hub.AudienceEditorProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=df1f6b11d06cf6ca" />
</tagMapping>
I was expecting the AudienceEditor control to be replaced by the new class in the EditForm.aspx and NewForm.aspx, but this is not working. I tried to debug the custom class, but execution doesn't enter the project, which means the AudienceEditor control doesn't get replaced.
The view source tells that the Audience field is rendered by the Microsoft.Office.Server.WebControls.FieldTypes.SPFieldTargetTo class, which contains the Microsoft.Office.Server.WebControls.FieldTypes.SpFieldTaretToControl, which in turn houses the AudienceEditor control. I have tried inheriting from all these 3 controls and tagmapped them, but it is simply not working.
Just to test I have mapped the TextBox control too and that works fine on the same page. It also comes into Debug. But the AudienceEditor is simply not working.
All I can think of is that you are seeing this behaviour because the tagMappings are applied at compile time, and the parent control that instantiates the AudienceEditor as a child control is already compiled into it's own dll.
Some alternative solutions that I think would do the job.
use css to display an image with your text as a background image,
positioned where you want it.
use javascript to inject the text when
the document has been loaded in the browser
use control adapters to
control rendering of the Audience Editor control (not very easy to
deploy when we're talking SharePoint)

Do need to databind in gridview?

If dt_grid.Rows.Count > 0 Then
dt_grid.DataSource = dt_grid
Else
MessageBox.Show("Not Found Data")
End If
I know that if get datatable in gridview.datasource,ever write gridview.Databind.But I found coding of my friend. He write only get datatable in gridview.datasource but no write gridview.dataBind. Therefore, his coding is not error. Why? Don't need to dataBind?
Are you sure your friend is not using the DataSourceID property?
Here is the difference from MSDN:
When the DataSourceID property is set (instead of the DataSource property), the data-bound control automatically binds to the data source control at run time.
Please take a look at this article and this one.
Hope it helped.
I took the time to do a small example to demonstrate that you need to call DataBind() on a normal asp.net gridview in order to render its data.
If your friend is not calling this, I am guessing that he is binding the grid to a data source in the .aspx code (to a SQLDatasource, on even an ObjectDataSource) and he is modifying that datasource in the code.
Please take a look at the following example:
The default.aspx page:
<%# Page Title="Home Page" Language="C#" AutoEventWireup="true"
CodeBehind="Default.aspx.cs" Inherits="GridViewDemo._Default" %>
<form id="form1" runat="server">
<asp:gridview id="GridView1" runat="server">
<Columns>
<asp:BoundField DataField="ID" />
<asp:BoundField DataField="Name" />
</Columns>
</asp:gridview>
</form>
The codebehind for the page: (it is in C# but I think it is relevant to VB as well)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace GridViewDemo
{
public partial class _Default : System.Web.UI.Page
{
public class Entity
{
public int ID { get; set; }
public string Name { get; set; }
}
protected void Page_Load(object sender, EventArgs e)
{
List<Entity> source = new List<Entity>() { new Entity() { ID = 1, Name = "First" }, new Entity() { ID = 2, Name = "Second" } };
GridView1.DataSource = source;
GridView1.DataBind();
// if you comment this line and run, the gridview is not rendered
}
}
}
Let me know if this answered your question.