- Průzkumník
- Nástroje ~ Možnosti složky…
- Typy souborů
- Najít „Složka“, „Folder“, nebo něco takového, co odpovídá verzi OS
- Upřesnit
- Nová… (akce)
- Akce: „Start Command Prompt Here“, Aplikace: „cmd.exe“
- OK, OK, OK, OK …
Author Archives: Robert Haken
Vista: Nelze nainstalovat síťovou tiskárnu z Windows XP, chyba 0x00000035
Windows Vista Vás při pokusu o instalaci síťové tiskárny hostované na stroji Windows XP mohou obdařit elegantní chybovou hláškou o nemožnosti instalace s chybovým kódem 0x00000035. Obecně se jedná o chybu nenalezení síťové cesty, a to je i většinou problém Windows Vista při instalaci tiskárny. Windows Vista se totiž snaží tiskárnu nainstalovat nikoliv pomocí UNC share názvu, např. \\HAVIT\Printer1, ale pomocí názvu tiskárny, něco jako \\HAVIT\HP LaserJet P2015 PCL6, což se nepodaří.
Dá se postupovat jednou z následujících cest:
- Tiskárny, Přidat novou tiskárnu, Síťovou tiskárnu, Není zobrazena, dle názvu = \\HAVIT\Printer1
- Tiskárnu, Přidat novou tiskárnu, Lokální, Nový port, Název portu = \\HAVIT\Printer1 (a nebo jiný způsob přidání portu, např. přes Vlastnosti serveru)
Avšak pozor, ani tento postup mně nepomohl, při pokusu o přidání portu jsem dostával hlášku Přístup odepřen, přestože jsem byl k hostitelskému počítači přihlášen s právy administrátora. Pomohlo na dobu instalace dát na tiskárně všechna práva skupině Everyone. Po úspěšné instalaci ve Vistách jsem práva zase odebral a nechal jen Tisk a vše v pohodě chodí.
Lokalizace skinů (Themes)
Nevím jak vás, ale mně už delší dobu rozčiluje, že skiny nejsou lokalizovatelné. Pánové z Microsoftu na to buď zapomněli, nebo nevím, každopádně se tak skiny staly do značné míry nepoužitelnými pro lokalizované aplikace.
Štvalo mě to tak dlouho, až jsem napsal vlastní lokalizaci skinů. Není to nic překrásného, protože jen máloco je v ASP.NET tak zapouzdřeno jako témata a skiny a tak se kód hemží reflexí – nicméně to výborně funguje k tomu účelu, na který to sám potřebuji – do lokalizovaných webových aplikací s běžným provozem bez masové zátěže.
Optimalizace opět ponechám na každém z vás, stejnětak případný příhodnější způsob integrace do ASP.NET. Níže popisuji jednoduché řešení, jehož účelem je pochopení použitého způsobu lokalizace skinů. Taktéž předesílám, že se jedná o explicitní metodu lokalizace, protože implicitní lokalizaci nepovažuji za obecně vhodnou.
Jak lokalizace skinů funguje
První, co každý asi zkusí, je použít ve skinu běžnou expression <%$ Resources: MyResources, MyValueName %>. ASP.NET Vás odmění krásnou chybovou hláčkou parseru: „Expressions are not allowed in skin files.“ Tudy cesta nevede…
Inspiroval jsem se tedy v syntaxi lokalizace, kterou používá XmlSiteMapProvider pro web.sitemap soubory, tedy např.:
<siteMapNode url="~/default.aspx" title="$resources:MyResources,MyResourceKey,My default value" />
a řekl jsem si, že stejnou syntaxi bych mohl použít i pro lokalizaci skinů:
<asp:Button Text="$resources: MyResource, MyValue, Default Value" runat="server" /> <asp:Button SkinID="SkinWithDefault" Text="$resources: MyResource, InvalidResource, Default Value" runat="server" />
…samozřejmě se tím odepře možnost použití textů začínajících znaky „$resource:“, ale to mi nevadí.
Teď ještě vymyslet, kde a jak v životním cyklu stránky zajistit odchytávání a vyhodnocování těchto lokalizačních výrazů. Po chvilce bádání s Reflectorem lze dospět k následujícím poznatkům:
- téma stránky je uložena v private fieldu Page._theme typu PageTheme
- stylesheet-téma stránky je uloženo v private fieldu Page._styleSheet typu PageTheme
- témata se inicializují (InitializeThemes) po ve fází PreInit životního cyklu stránky, avšak až po události PreInit (v obsluze události PreInit tedy zřejmě není možné témata modifikovat),
- témata se aplikují ve fázi Init životního cyklu stránky, avšak před událostí Init (v obsluze události Init je tedy pozdě je modifikovat),
- třída PageTheme má metodu ApplyControlSkin(Control control), která najde ve slovníku skinů skin (typ ControlSkin) příslušející danému controlu a aplikuje ho pomocí skin.ApplySkin(Control control)
- metoda ControlSkin.ApplySkin(Control control) nedělá nic jiného, než že zavolá vygenerovaného delegáta.
public void ApplySkin(Control control)
{
this._controlSkinDelegate(control);
}
Zdánlivě nikde není rozumný prostor pro modifikaci skinu, nikde není seznam hodnot skinu, žádná property-value kolekce, kde by se dal resource-odkaz najít a modifikovat. Vygenerovaný delegát prostě natvrdo nastavuje vlastnosti příslušného controlu a hotovo.
…zde však přichází to kouzlo! Můžeme totiž delegáta, který je uložen v private fieldu _controlSkinDelegate vyměnit za delegáta vlastního. A to nejenomže ho můžeme vyměnit, ale můžeme si dokonce odkaz na původního delegáta uchovat a tuto původní metodu z našeho delegáta zavolat. Celý můj fígl tedy spočívá ve výměně tohoto delegáta, za jinou metodu, která však v sobě nejprve volá metodu původní (aplikuje skin) a následně prochází skinovatelné property controlu (pro jednoduchost pouze textové) a hledá v nich lokalizující resource-odkaz, který případně vyhodnotí. Abychom si měli původního delegáta jak uchovat, je nová metoda wrapována do instance třídy SkinLocalizationDelegateWrapper.
Poslední, co zbývá vyřešit, je jak se s vlastní obsluhou dostat do správného místa životního cyklu stránky, tedy za InitializeTheme(), které je po události PreInit a před ApplySkin(), které je na začátku Init fáze, před událostí Init. Nehledal jsem dlouho, a možná najdete vhodnější místo, ale já jsem pro tyto účely znásilnil virtuální metodu Control.ResolveAdapter(), která se volá na začátku InitRecursive(), tedy přesně v místě, kde se nám to hodí.
SkinLocalizationPageBase
Vytvořil jsem tedy nakonec třídu SkinLocalizationPageBase, která je potomkem System.Web.UI.Page, a která by měla být předkem všech stránek webu, kde lokalizované skiny používáme (nejlépe tedy úplně všech, pomocí <pages pageBaseType=“SkinLocalizationPageBase“ /> ve web.configu.
Výsledný kód vypadá takto:
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Web.UI.Adapters;
using System.Reflection;
using System.Collections;
using System.Globalization;
using System.Resources;
namespace Havit.Web.UI
{
/// <summary>
/// Bázová třída pro stránky, které obsahují controly s lokalizovatelnými skiny.
/// </summary>
public class SkinLocalizationPageBase : Page
{
#region ResolveAdapter (override)
/// <remarks>
/// Metoda ResolveAdapter je jediné místo, které jsem našel, že se pomocí něj dá vykonávat nějaký kód
/// mezi InitializeTheme() (po OnPreInit) a ApplyControlSkin (před OnInit).
/// </remarks>
protected override ControlAdapter ResolveAdapter()
{
// Theme
FieldInfo themeFieldInfo = typeof(Page).GetField("_theme", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
PageTheme theme = themeFieldInfo.GetValue(this) as PageTheme;
if (theme != null)
{
PropertyInfo controlSkinsPropertyInfo = typeof(PageTheme).GetProperty("ControlSkins", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
IDictionary controlSkins = controlSkinsPropertyInfo.GetValue(theme, null) as IDictionary;
foreach (DictionaryEntry entry in controlSkins)
{
ControlSkin controlSkin = entry.Value as ControlSkin;
if (controlSkin != null)
{
FieldInfo controlSkinDelegateFieldInfo = typeof(ControlSkin).GetField("_controlSkinDelegate", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
ControlSkinDelegate controlSkinDelegate = controlSkinDelegateFieldInfo.GetValue(controlSkin) as ControlSkinDelegate;
SkinLocalizationDelegateWrapper sldw = new SkinLocalizationDelegateWrapper(controlSkinDelegate);
controlSkinDelegateFieldInfo.SetValue(controlSkin, new ControlSkinDelegate(sldw.DoWork));
}
}
}
// StylesheetTheme
FieldInfo styleSheetFieldInfo = typeof(Page).GetField("_styleSheet", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
PageTheme styleSheet = styleSheetFieldInfo.GetValue(this) as PageTheme;
if (styleSheet != null)
{
PropertyInfo controlSkinsPropertyInfo = typeof(PageTheme).GetProperty("ControlSkins", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
IDictionary controlSkins = controlSkinsPropertyInfo.GetValue(styleSheet, null) as IDictionary;
foreach (DictionaryEntry entry in controlSkins)
{
ControlSkin controlSkin = entry.Value as ControlSkin;
if (controlSkin != null)
{
FieldInfo controlSkinDelegateFieldInfo = typeof(ControlSkin).GetField("_controlSkinDelegate", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
ControlSkinDelegate controlSkinDelegate = controlSkinDelegateFieldInfo.GetValue(controlSkin) as ControlSkinDelegate;
if (!(controlSkinDelegate.Target is SkinLocalizationDelegateWrapper))
{
SkinLocalizationDelegateWrapper sldw = new SkinLocalizationDelegateWrapper(controlSkinDelegate);
controlSkinDelegateFieldInfo.SetValue(controlSkin, new ControlSkinDelegate(sldw.DoWork));
}
}
}
}
return base.ResolveAdapter();
}
#endregion
#region SkinLocalizationDelegateWrapper
/// <summary>
/// SkinLocalizationDelegateWrapper obalí původní výkonný kód skinování a zajistí vyhodnocení resource-odkazů.
/// </summary>
class SkinLocalizationDelegateWrapper
{
private ControlSkinDelegate _oldControlSkinDelegate;
public SkinLocalizationDelegateWrapper(ControlSkinDelegate oldControlSkinDelegate)
{
this._oldControlSkinDelegate = oldControlSkinDelegate;
}
public Control DoWork(Control control)
{
Control result = null;
if (this._oldControlSkinDelegate != null)
{
result = this._oldControlSkinDelegate(control);
}
PropertyInfo[] properties = result.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.PropertyType == typeof(String))
{
// zajímají nás jen property typu string
ThemeableAttribute themableAttribute = Attribute.GetCustomAttribute(property, typeof(ThemeableAttribute)) as ThemeableAttribute;
if ((themableAttribute == null) || themableAttribute.Themeable)
{
// které nemají atribut themable vůbec, nebo ho nemají s volbou false (výchozí je true)
string propertyValue = property.GetValue(result, null) as string;
if (((propertyValue != null) && (propertyValue.Length > 10)) && propertyValue.ToLower(CultureInfo.InvariantCulture).StartsWith("$resources:", StringComparison.Ordinal))
{
string resourceOdkaz = propertyValue.Substring(11);
if (resourceOdkaz.Length == 0)
{
throw new ConfigurationErrorsException("Resource odkaz skinu nesmí být prázdný.");
}
string resourceClassKey = null;
string resourceKey = null;
int length = resourceOdkaz.IndexOf(',');
if (length == -1)
{
throw new ConfigurationErrorsException("Resource odkaz skinu není platný");
}
resourceClassKey = resourceOdkaz.Substring(0, length);
resourceKey = resourceOdkaz.Substring(length + 1);
string defaultPropertyValue = null;
int index = resourceKey.IndexOf(',');
if (index != -1)
{
defaultPropertyValue = resourceKey.Substring(index + 1); // default value
resourceKey = resourceKey.Substring(0, index);
}
else
{
propertyValue = null;
}
try
{
propertyValue = (string)HttpContext.GetGlobalResourceObject(resourceClassKey.Trim(), resourceKey.Trim());
}
catch (MissingManifestResourceException)
{
// NOOP
}
if (propertyValue == null)
{
propertyValue = defaultPropertyValue;
}
property.SetValue(result, propertyValue, null);
}
}
}
}
return result;
}
}
#endregion
}
}
Úplné demo v podobě Web Site je v připojeném ZIP souboru.
Související články
Attachment SkinLocalization.zip
Office SharePoint Designer ničí odkazy v .master souborech
Dvě hodiny jsem právě strávil s obligátní chybou „File Not Found.“, problém se nakonec ukázal v tom, že Microsoft Office SharePoint Designer při otevření default.master souboru změnil v direktivách <%@ Register %> atribut src z
<%@ Register TagPrefix="wssuc" TagName="Welcome" src="~/_controltemplates/Welcome.ascx" %>
na
<%@ Register TagPrefix="wssuc" TagName="Welcome" src="_controltemplates/Welcome.ascx" %>
…zabít málo!!!
Zobrazování podrobností výjimek a chyb
Jestli Vás taky rozčilují chybové hlášky SharePointu ve stylu „Unexpected error has occured.“ nebo „File not found.“ a marně hledáte detaily těchto chyb/výjimek, pak vězte, že stačí ve web.configu nastavit v elementu SharePoint/SafeMode atribut CallStack na true:
<SharePoint>
<SafeMode MaxControls="200" CallStack="true" ...
Samozřejmě k tomu ještě <customErrors mode=“Off“ /> nebo mode=“RemoteOnly“:
<customErrors mode="Off" />
…a je to.
Samozřejmě na produkční mašině bychom měli mít CallStack=“false“.
HttpException: The Controls collection cannot be modified because the control contains code blocks
Tuto krásnou výjimku můžeme znenadání dostat (mimo jiné), pokud budeme v controlu <head runat=“server“> používat code-bloky, např.
<head runat="server">
<title>HAVIT Goran</title>
<link rel="stylesheet" type="text/css" href="~/templates/styles/havit.css" />
<link rel="stylesheet" type="text/css" href="~/templates/styles/global.css" />
<script type="text/javascript" src="<%= ResolveUrl("~/templates/scripts/HavitScripts.js") %>"></script>
<asp:ContentPlaceHolder ID="HeadtailCPH" runat="server" />
</head>
Onen ďábelský blok <%= … %> nemusí zpočátku vůbec vadit, pokud však někdy později do stránky přidáme funkcionalitu, která by chtěla měnit Page.Header, např. umístíme do stránky control, který si bude chtít přilinkovat přes Header vlastní styly a skripty, pak budeme obšťastněni krásnou výjimkou System.Web.HttpException:
The Controls collection cannot be modified because the control contains code blocks (i.e. <% … %>).
…lepší je tedy code-bloky <% %>, resp. <%= %> v <head runat=“server“> nepoužívat (ještě lépe je nepoužívat vůbec) a nahradit je jiným způsobem. V tomto konkrétním případě třeba za <%# ResolveUrl(…) %> data-bindovací konstrukci (samozřejmě pak musíme volat Page.Header.DataBind()).
Programátorská hádanka – neprobádaná zákoutí C#
Víte co znamená @ v následujícím bloku kódu?
ICollection @is = dataSource as ICollection;
if (@is != null)
{
this.Items.Capacity = @is.Count + this.Items.Count;
}
Odpověď najdete o řádku níže napsanou bílým písmem (pro odtajnění třeba označte):
>>> Zavináč je možné použít na začátku identifikátoru, pokud chceme, aby identifikátorem mohlo být i klíčové slovo (is, null, for, …) <<<
…a teď se přiznejte, kdo jste to znal?
Proč nejde udělat rozumný AjaxValidator?
Po půl dni snažení a bádání jsem dospěl k subjektivnímu závěru, že nelze udělat rozumný ASP.NET validátor založený na AJAXu. Přesněji řečeno bylo mým cílem vytvořit obdobu CustomValidatoru, který by měl běžný ServerValidate a navíc klientskou jscript část, která by pomocí AJAXového HTTP-requestu validovala hodnotu vůči tomuto ServerValidate.
Narazil jsem na následující zásadní překážky, které podle mě nelze snadno překonat:
- Současné ASP.NET validátory, resp. všechny jejich Microsoftí obslužné klientské skripty a API jsou nekompromisně synchronní. V okamžiku požadavku validace (obvykle před submitem stránky) jsou volány prosté validační funkce všech povolených validátorů a od těchto se očekává jen true/false dle výsledku validace. V současném konceptu validátorů nikde není rozumný prostor pro asynchronní operace či nějakého zásahu do průběhu validace.
- Je prakticky nemožné rozumným způsobem provést synchronní AJAXové volání vůči serveru a stejnětak je nemožné rozumně toto synchronní volání simulovat nějakým blokováním threadu uvnitř validační funkce. Javascript nezná žádné sleep/wait/pause a veškeré snahy o jeho implementaci vždy končí v tupé smyčce zatěžující CPU ze 100% do okamžiku splnění nějaké podmínky (přijetí AJAXového callbacku, dosažení určitého času, naplnění čítače, atp.)
Pro současný koncept ASP.NET validátorů jsem dospěl k těmto závěrům:
- Protože je koncept založen především na jednorázové kontrole hodnot před submitem formuláře a má za účel tento submit povolit/zakázat, je oprávněně založen na synchronních operacích a pro jakékoliv dlouhotrvající operace či asynchronní volání zde není prostor a poměrně logicky se s nimi nepočítá.
- Asynchronní AJAXová validace vůči serveru by byla použitelná u konceptu průběžné validace, kdy by byly hodnoty kontrolovány ihned po jejich zadání (onchange) a takováto validace by díky svému zpoždění nemohla blokovat odeslání formuláře, spíše by sloužila jako proaktivní kontrola uživatelského zadání.
- Současný koncept validátorů připouští pouze zběsilá řešení se synchronizačním blokováním threadu v různých sleep smyčkách (byť s timeoutem), popř. „chováním nekompatibilní“ on-change implementace dle bodu 2.
Veškeré pokusy o implementace AjaxValidatoru, které jsem na netu viděl, trpěly jedním nebo několika nedostatky z výše uvedených a jejich praktická použitelnost se tak blíží nule (spíše se tak stávají přetěžovači serverů, než účinnými validátory).
Avšak! Pokud by se našel někdo, koho by napadlo, jak výše uvedená omezení účinně obejít a funkční AjaxValidator vytvořit, sem s myšlenkou a pochvala ho nemine… ;-)))
PS: Sám jsem dospěl k implementaci AjaxValidatoru, který měl nastavitelný Timeout a ve validační funkci po zavolání asynchronního AJAX requestu na ServerValidate čekal po dobu tohoto timeoutu na odezvu serveru (čekal = různé hnusné implementace zatěžujících smyček čekajících na nějaký příznak nebo timeout). Pokud do Timeoutu nebyl AJAXem výsledek od serveru získán, propustila klientská část validaci jako IsValid=true, a tak se to v případě submitu zvalidovalo na serveru, nebo v případě onchange validace dovalidovalo později asynchronně (až dorazil callback, tak se aktualizoval validátor). Výsledek mi přišel pro praxi nepoužitelný, už timeout 1s je pro ovládání webového formuláře nepříjemný, stránka nereaguje na odeslání formuláře okamžitě, proto jsem to celé zahodil… Můj poznatek je, že pokud validátorem chápeme to, co ASP.NET, tedy blokaci odeslání formuláře, tak přes AJAX cesta nevede, i kdyby to šlo bez toho cyklického pollingu s timeoutem. Leda by byla odezva v řádu milisekund, což při serverových validacích moc nehrozí (obvykle nějaký dotaz do DB na exists, atp.).
Šířka tlačítek v IE (input type=“button“)
V Internet Exploreru je docela alchymie vyrobit tlačítko, které je široké stejně jako text na něm.
<input type="button" value="Text tlačítka" class="button"/> <asp:Button ID="MyButton" Text="Text tlačítka" class="button"/>
Co jinde funguje, v IE selhává:
.button {
margin:0;
padding:0;
}
Co však kupodivu pomůže, je nastavení:
.button{
padding:0;
width:auto;
overflow:visible;
}
…někdy je lepší nebádat, proč tomu tak je.
Office: Změna product key
Po instalaci Office (2007, ale bude to stejné) mi byla odmítnuta aktivace, z té haldy našich MSDNek jsem vybral použité číslo. Aktivace byla odmítnuta, možnost změny product key pomocí UI jsem však nikde nenašel.
Pomůže tedy smazání následujícího klíče z registrů:
HKLM\SOFTWARE\Microsoft\Office\12.0\Registration\{GUID}\DigitalProductID
Při následném spuštění kterékoliv aplikace z Office se spustí Office-instalátor, který se zeptá na nový Product Key a následně se sice ptá na „instalaci“, nicméně ve skutečnosti udělá jen konfiguraci a PK je nastaven. Opakované spuštění Office aplikace vyvolá možnost aktivace s novým PK.
Update pro Office 2013
Product key pro Microsoft Office 2013 lze změnit bez zásahu do registrů. Z ovládacích panelů zvolit Programy a funkce, zde najít Microsoft Office 2013, kliknout pravým tlačítkem myši, zvolit Změnit. V zobrazeném dialogu vybrat „Zadat kód Product Key“.