Making Intellisense see resources generated by custom MSBuild target (WPF) - wpf

In my net4.8 WPF project I have an Art folder which contains .svg files.
I have created an MSBuild target which:
Invokes inkscape to convert .svg files to .xaml, placing the generated files in the project's intermediate output directory.
Includes the generated .xaml files into the target binary as resources.
Then, in the XAML of my application, I have <Frame> elements that reference the .xaml resources to display the images.
So far, so good; all this works.
The problem is that when I do this, intellisense knows nothing of my generated resource files. I would like intellisense to know those files, and give me suggestions, because the application is bound to have lots of them, so using blind binding-by-name is soon going to be problematic.
Steps to reproduce the problem:
STEP 1: make sure you have a relatively recent version of inkscape installed and accessible via your path.
STEP 2: Create a new C# Windows Desktop "WPF App (.NET Framework)" application project (+solution) targeting .Net Framework 4.8.
STEP 3: Add a XamlFromSvg.targets file with the following content:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Ensure Visual Studio puts the XamlFromSvg item in the Build Action dropdown -->
<ItemGroup>
<AvailableItemName Include="XamlFromSvg"/>
</ItemGroup>
<!-- Instruct MsBuild to include our "Compile" and "Resource" targets in the Build -->
<PropertyGroup>
<BuildDependsOn>
XamlFromSvgCompile;
XamlFromSvgResource;
$(BuildDependsOn);
</BuildDependsOn>
</PropertyGroup>
<!-- Convert ("compile") .svg to .svg.xaml -->
<Target Name="XamlFromSvgCompile" Condition="#(XamlFromSvg)!=''" BeforeTargets="BeforeBuild;BeforeRebuild"
Inputs="#(XamlFromSvg)"
Outputs="$(IntermediateOutputPath)X\%(XamlFromSvg.Identity).xaml">
<Message Importance="High" Text="XamlFromSvgCompile: %(XamlFromSvg.Identity) -> $(IntermediateOutputPath)X\%(XamlFromSvg.Identity).xaml"/>
<MakeDir Directories="$(IntermediateOutputPath)X\%(XamlFromSvg.RelativeDir)" Condition="!Exists('$(IntermediateOutputPath)X\%(XamlFromSvg.RelativeDir)')"/>
<Exec Command="cmd /c inkscape "%(XamlFromSvg.Identity)" --export-filename="$(IntermediateOutputPath)X\%(XamlFromSvg.Identity).xaml""
Outputs="$(IntermediateOutputPath)X\%(XamlFromSvg.Identity).xaml">
</Exec>
</Target>
<!-- Add .svg.xaml as resource -->
<Target Name="XamlFromSvgResource" Condition="#(XamlFromSvg)!=''">
<ItemGroup>
<GeneratedXamlFromSvg Include="$(IntermediateOutputPath)X\%(XamlFromSvg.Identity).xaml"/>
</ItemGroup>
<Message Importance="High" Text="XamlFromSvgResource: $(IntermediateOutputPath)X\#(XamlFromSvg.Identity).xaml -> %(GeneratedXamlFromSvg.Identity)"/>
<CreateItem Include="%(GeneratedXamlFromSvg.Identity)">
<Output TaskParameter="Include" ItemName="Resource"/>
</CreateItem>
</Target>
<!--This is here just to prevent "unknown item group" warnings. -->
<ItemGroup>
<XamlFromSvg Include="foo" Condition="False"/>
</ItemGroup>
</Project>
STEP 4: Near the end of WpfApp1.csproj, after the <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> line, add the following line:
<Import Project="XamlFromSvg.targets" />
STEP 5: Add an /Art folder with an .svg file (Say, Misc.HamburgerMenu.svg.) You can use the following sample .svg file:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg8" width="500" height="500" viewBox="0 0 500 500">
<g id="g6" transform="matrix(27.777778,0,0,27.777778,-83.333334,-81.333336)">
<path d="M 3,6 H 21 V 8 H 3 V 6 m 0,5 h 18 v 2 H 3 v -2 m 0,5 h 18 v 2 H 3 Z" id="path4" />
</g>
</svg>
STEP 6: From within Solution Explorer, right-click on the .svg file and select "Include in project".
STEP 7: Right-click on the .svg file again, select "Properties", and set the Build Action to XamlFromSvg.
STEP 8: In App.xaml insert the following:
<Frame x:Shared="false" x:Key="Icon.Misc.HamburgerMenu" Source="/X/Art/Misc.HamburgerMenu.svg.xaml" />
STEP 9: In MainWindow.xaml, replace <Grid></Grid> with the following:
<ContentControl Content="{StaticResource Icon.Misc.HamburgerMenu}" Margin="50"/>
STEP 10: Build and run; You should see a window displaying your .svg file.
STEP 11: Now go back to App.xaml. If you place the cursor on /X/ and hit Ctrl+Space.
Intellisense will suggest anything but /X/Art/Misc.HamburgerMenu.svg.xaml. Intellisense knows nothing of this resource.
And ideas how to solve this problem or achieve the same result by other means would be welcome.
(However, any ideas aiming to achieve the same result by other means must actually achieve the same result, which is to begin with .svg files and to have these files displayed in a net4.8 WPF application without the need to perform any manual steps whatsoever.)
Note: I know netcore supports .svg files, but I am sticking with net4.8 for now, which does not. And I have heard of 'XAML Islands', it seems like a clunky additional moving part held together by shoestrings, so I do not want to try it.
Also, any suggestions on how to improve XamlFromSvg.targets would be welcome. (I just compiled it from samples, I hardly know what I am doing.)

Related

Check whether a file exists in an Ant FileSet

I am using FileSet in an Ant build file that is being read as an input from external source. How can I check if a specific file exists in the FileSet?
For example, if the FileSet being read is **/src/*.java, I should be able to find MyClass.java in it. However, if the FileSet is **/src/AClass.java, **/src/BClass.java, then MyClass.java is not in it.
Even if there is a way I can expand the FileSet and do a string contains check, then that should work.
There are plenty of ways to create selectors within a FileSet, but there is nothing I could find that tells how to find/select a File in a given FileSet. So I can't back it up with an example that I could try. Any help will be very appreciated.
Using the restrict resource collection to include only the named file:
<project ... xmlns:rsel="antlib:org.apache.tools.ant.types.resources.selectors">
...
<restrict id="filtered.fileset">
<fileset refid="source.fileset"/>
<rsel:name name="MyClass.java"/>
</restrict>
<condition property="file.found" value="yes">
<resourcecount when="greater" count="0" refid="filtered.fileset" />
</condition>
I implemented that by doing an intersection of the complete Fileset with the Fileset containing just this one file that I am looking, and verifying that if the count equals 1. The other conditions can be easily AND-ed or OR-ed.
<target name="checkSomething">
<condition property="something.present">
<and>
<available file="/src/theFile"/>
<resourcecount when="equal" count="1">
<intersect>
<fileset dir="${src.dir}" includes="*"/>
<fileset dir="${src.dir}" includes="**/src/something.java"/>
</intersect>
</resourcecount>
</and>
</condition>
<echo message="Something present: ${something.present}"/>
</target>

Use Ant to find file with latest version number

I want to use ant to find the file with the latest version number. For example, I have a file directory named tomcat with the following files:
apache-tomcat-6.0.37.zip
apache-tomcat-6.0.38.zip
apache-tomcat-6.0.39.zip
I want ant to determine that apache-tomcat-6.0.39.zip is the latest file. Is there a way to do this?
Thanks!
Use resource collections, see last and sort. f.e. :
<project>
<path id="foo">
<last>
<sort>
<fileset dir="C:/some/path" includes="**/apache-tomcat-*.zip"/>
</sort>
</last>
</path>
<echo>$${foo} => ${toString:foo}</echo>
</project>
output :
[echo] ${foo} => C:\some\path\apache-tomcat-6.0.39.zip
use <pathconvert>to get file names from the relevant <fileset>.
use <sortlist> to sort the filenames in natural String order.
pick the last filename from the list.
try this:
<fileset dir="${your.base.dir}" id="one">
<include name="**/apache-tomcat-.*.zip"/>
</fileset>
<pathconvert property="orig.list" refid="one" pathsep=","/>
<sortlist property="sorted.list" value="${orig.list}" delimiter="," />
<propertyregex property="result" input="${sorted.list}" regexp=",?([^,]+?)$" select="\1"/>
inputList: <property name="one" value="a.b-2,a.b-5,a.b-1,a.b-3,a.b-4"/>
outputList: [echo] a.b-5

Ant script -- split string and access via indexing

I need to write an Ant script which would load a properties file, read a single property out of it. The value (multiline) is something like:
path/to/file1a;path/to/file1b,
path/to/file2a;path/to/file2b,
..
..
I need to iterate over every line, and execute a shell command which looks like:
myCommand -param1 path/to/file1a -param2 path/to/file1b #Command inside a single iteration
I have able to figure out how to loop:
<for list="${ValueFromPropertyFile}" param="a">
<sequential>
<exec executable="myCommand">
<arg value="-param1" />
<arg value="---- split(#{a}, ";")[0] ----" />
<arg value="-param2" />
<arg value="---- split(#{a}, ";")[1] ----" />
</exec>
</sequential>
</for>
This is quite a simple task in my opinion. I tried searching, but without any success.
I would appreciate if someone could help me out with this, or point me to a relevant document.
Many thanks,
Pratik
Couple of problems with your assumptions:
The format of your input file is not a standard Java properties file, so it can't be loaded using the standard loadproperties task in ANT.
ANT is not a programming language. The "for" task you've quoted is not part of core ANT (Requires the 3rd party ant-contrib.jar)
So I'd suggest using an embedded script to solve your problem.
Example
The project is self documenting:
$ ant -p
Buildfile: /home/mark/tmp/build.xml
This is a demo project answering the following stackoverflow question:
http://stackoverflow.com/questions/14625896
First install 3rd party dependencies and generate the test files
ant bootstrap generate-test-files
Then run the build
ant
Expect the following output
parse-data-file:
[exec] build/myCommand -param1 path/to/file1a -param2 path/to/file1b
[exec] build/myCommand -param1 path/to/file2a -param2 path/to/file2b
build.xml
<project name="demo" default="parse-data-file">
<description>
This is a demo project answering the following stackoverflow question:
http://stackoverflow.com/questions/14625896
First install 3rd party dependencies and generate the test files
ant bootstrap generate-test-files
Then run the build
ant
Expect the following output
parse-data-file:
[exec] build/myCommand -param1 path/to/file1a -param2 path/to/file1b
[exec] build/myCommand -param1 path/to/file2a -param2 path/to/file2b
</description>
<target name="bootstrap" description="Install 3rd party dependencies">
<mkdir dir="${user.home}/.ant/lib"/>
<get dest="${user.home}/.ant/lib/groovy-all.jar" src="http://search.maven.org/remotecontent?filepath=org/codehaus/groovy/groovy-all/2.1.0/groovy-all-2.1.0.jar"/>
</target>
<target name="generate-test-files" description="Generate the input data and sample script">
<echo file="build/input.txt">path/to/file1a;path/to/file1b,
path/to/file2a;path/to/file2b,</echo>
<echo file="build/myCommand"> #!/bin/bash
echo $0 $*</echo>
<chmod file="build/myCommand" perm="755"/>
</target>
<target name="parse-data-file" description="Parse data file">
<taskdef name="groovy" classname="org.codehaus.groovy.ant.Groovy"/>
<groovy>
new File("build/input.txt").eachLine { line ->
def fields = line.split(/[;,]/)
ant.exec(executable:"build/myCommand") {
arg(value:"-param1")
arg(value:fields[0])
arg(value:"-param2")
arg(value:fields[1])
}
}
</groovy>
</target>
<target name="clean" description="Cleanup build files">
<delete dir="build"/>
</target>
</project>

iBatis - Why is sqlMapConfig.xml unable to find the sql maps defined in it?

I have a sqlMapConfig.xml that has three SQLMaps defined in it.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<!-- Statement namespaces are required for Ibator -->
<settings enhancementEnabled="true" useStatementNamespaces="true"/>
<!-- Setup the transaction manager and data source that are
appropriate for your environment
-->
<transactionManager type="JDBC">
<dataSource type="SIMPLE" >
<property name="JDBC.Driver"
value="com.mysql.jdbc.Driver"/>
<property name="JDBC.ConnectionURL"
value="jdbc:mysql://localhost:3306/sug"/>
<property name="JDBC.Username"
value="root"/>
<property name="JDBC.Password"
value="admin"/>
</dataSource>
</transactionManager>
<!-- SQL Map XML files should be listed here -->
<sqlMap resource="com/tatakelabs/dbmaps/categories_SqlMap.xml" />
<sqlMap resource="com/tatakelabs/dbmaps/pro_SqlMap.xml" />
<sqlMap resource="com/tatakelabs/dbmaps/pro_category_SqlMap.xml" />
</sqlMapConfig>
I get a runtime error - Cause: java.io.IOException: Could not find resource com/tatakelabs/dbmaps/categories_SqlMap.xml
categories_SqlMap.xml is present in that location. I tried changing the location of the map xml, but that did not help. sqlMapConfig.xml validates against the DTD. categories_SqlMap.xml also validates against the right DTD. I am at my wits end trying to figure out why it can't find the resource. The sqlMap files are generated by iBator.
This was happening because the sqlmap file location was not getting copied to target. Added a copy goal and that fixed it.
I had the same problem. It appears the problem lies with the location of the config file. Thus, its in relation of the project resource structure.
I moved the config file in the same package as the mapper classes and it worked. In this case try moving all the resources to this package and update the resource attributes to:
<sqlMap resource="categories_SqlMap.xml" />
<sqlMap resource="pro_SqlMap.xml" />
<sqlMap resource="pro_category_SqlMap.xml" />
Solved it.
I moved the xml file to where the Pojo was located and provided the path as follows:
<sqlMap resource="com/heena/ibatis/model/jsr/jsr.xml" />
And it worked.
place it ...src\java\abc.xml under the Source Packages directory.
If you are using Spring, you can use a SqlMapClientFactoryBean specifying property "mappingLocations". In this property you can specify a generic path, such as "com/tatakelabs/dbmaps/*_SqlMap.xml" or with a variable such as ${mapfiles}, that will be resolved by Spring as an array of file names. This lets you omit sqlMap element in sqlMapConfig. This technique will run with iBatis 2.3.4 on. However sql-map-config-2.dtd is also contained inside iBatis.jar, so you can experience some parsing errors, as /com/ibatis/sqlmap/engine/builder/xml/sql-map-config-2.dtd may have a bug. In this case you may want to replace it inside the jar with the one from the URL:
http://ibatis.apache.org/dtd/sql-map-config-2.dtd.

What is the best practice for compiling Silverlight and WPF in one project?

I've just completed a Silverlight project and it's time to have a little clean up. I'd like to take my core files and put them into a separate project which I will reference from my main Silverlight app.
Some of these classes are compatible with WPF and I would quite like to be able to have Silverlight / WPF code all in one project. My ideal solution would be a single project that allows multiple configurations. So,
Configuration: Silverlight would generate:
Company.Controls.Silverlight.dll
Configuration: WPF would generate:
Company.Controls.Wpf.dll
Is it possible to have the same source in the same file just seperated via defines?
Has anyone done this before?
Edit: I've created a solution per project, like MyCompany.Windows.Controls, which then contains 2 projects, MyCompany.Windows.Controls & MyCompany.Windows.Controls.Silverlight. Alongside those 2 folders I have a "Shared" folder, which contains files used by both projects. It works well so far :)
Update: goes to show that there is almost always an easier way. :-)
The first step is to use conditional compilation to segregate the Silverlight specific code. (I'm assuming that your "default" target with be WPF.)
Secondly, you'll need a build script that will compile the code for each platform, setting the appropriate defines and assembly references.
Take a look at the open-source Caliburn project. It does all this.
Here's an example from Caliburn's ExtensionMethods class.
public static T GetResource<T>(this FrameworkElement element, object key)
{
DependencyObject currentElement = element;
while (currentElement != null)
{
var frameworkElement = currentElement as FrameworkElement;
if (frameworkElement != null && frameworkElement.Resources.Contains(key))
return (T)frameworkElement.Resources[key];
#if !SILVERLIGHT
currentElement = (LogicalTreeHelper.GetParent(currentElement) ??
VisualTreeHelper.GetParent(currentElement));
#else
currentElement = VisualTreeHelper.GetParent(currentElement);
#endif
}
if (Application.Current.Resources.Contains(key))
return (T)Application.Current.Resources[key];
return default(T);
}
If you open Caliburn in VS and compile it, it complies against the standard framework. The references are for .NET 3.5 and WPF, not Silverlight. That is also why the pre-processing directives are "!SILVERLIGHT".
In your build script (Caliburn uses NAnt), you'll have a target that sets the defines for each platform, for example, Caliburn's Silverlight target is:
<target name="config-platform-silverlight20">
<property name="nant.settings.currentframework" value="silverlight-2.0"/>
<property name="build.platform" value="silverlight-2.0"/>
<property name="build.defines" value="${global.build.defines},SILVERLIGHT,SILVERLIGHT_20,NO_WEB,NO_REMOTING,NO_CONVERT,NO_PARTIAL_TRUST,NO_EXCEPTION_SERIALIZATION,NO_SKIP_VISIBILITY,NO_DEBUG_SYMBOLS"/>
<property name="current.path.bin" value="${path.bin}/silverlight-2.0/${build.config}"/>
<property name="current.path.test" value="${path.bin}/silverlight-2.0/tests" />
<property name="current.path.lib" value="${path.lib}/Silverlight" />
</target>
Then here is the target that invoke the actual Silverlight build:
<target name="platform-silverlight20" depends="config">
<if test="${framework::exists('silverlight-2.0')}">
<echo message="Building Caliburn ${build.version} for Silverlight v2.0."/>
<call target="config-platform-silverlight20"/>
<copy todir="${current.path.bin}">
<fileset basedir="${current.path.lib}">
<include name="*.dll"/>
<include name="*.xml"/>
</fileset>
</copy>
<call target="core"/>
<call target="messaging"/>
<call target="actions"/>
<call target="commands"/>
<call target="package-platform"/>
</if>
<if test="${not(framework::exists('silverlight-2.0'))}">
<echo message="Silverlight v2.0 is not available. Skipping platform."/>
</if>
</target>
Finally, here is an example of the "core" target that is responsible for producing the Caliburn.Core.dll:
<target name="core" depends="config, ensure-platform-selected">
<mkdir dir="${current.path.bin}"/>
<csc keyfile="${path.src}/Caliburn.snk" noconfig="true" warnaserror="false" target="library" debug="${build.debug}" optimize="${build.optimize}" define="${build.defines}"
output="${current.path.bin}/Caliburn.Core.dll"
doc="${current.path.bin}/Caliburn.Core.xml">
<sources basedir="${path.src}">
<include name="${build.asminfo}"/>
<include name="Caliburn.Core/**/*.cs"/>
</sources>
<references basedir="${current.path.bin}">
<include name="mscorlib.dll"/>
<include name="System.dll"/>
<include name="System.Core.dll"/>
<!--WPF-->
<include name="PresentationCore.dll"/>
<include name="PresentationFramework.dll"/>
<include name="WindowsBase.dll"/>
<!--Silverlight-->
<include name="System.Windows.dll" />
</references>
<nowarn>
<warning number="1584"/>
</nowarn>
</csc>
</target>
Notice the way it's referencing the necessary assemblies.
You'll probably need to edit your NAnt.exe.config (if you are using NAnt) to match the correct version of the Silverlight framework. For Silverlight RTW, the framework version will be 2.0.31005.0.
I haven't tried it myself (still trying to find the time to play with Silverlight), but couldn't you have one solution with two projects, one targetting Silverlight and the other targetting .NET 3.5, and add the common class files to each project as Links (right-click the project, Add Existing Item..., Add as Link)?
** Update: See Mark's answer below regarding the Project Linker. I've been using this in my multi-targetted composite application with the PRISM 2.0 CAL and it's a beautiful thing. I don't think this existed in PRISM 1.0?
You should check out "patterns & practices: Composite WPF and Silverlight"
http://www.codeplex.com/CompositeWPF/Wiki/View.aspx?title=Home
It has quick starts with WPF/Silvelight versions of the same app in one solution.
Also a "Project Linker" that updates the source of your WPF app when you change Silverlight code (or vice versa) using linking. It can be overridden when you have version specific code.
The examples are still a little rough around the edges but it may give you an idea of how to go about your project.
HTH

Resources